/* ---------------------------------------------------------------------------
 * Dispatches resource — the canonical example of the resource pattern.
 *
 * The optional pulse sub-form is read out of ctx.formData by the create/update
 * handlers (pulse_question / pulse_options[] / pulse_opens_at / pulse_closes_at).
 * Status changes ride on the same Save submit: when the form's chosen status
 * differs from the current one, publishDispatch / archiveDispatch fire after
 * the content save.
 * ------------------------------------------------------------------------- */

import {
  createDispatch,
  updateDispatch,
  publishDispatch,
  archiveDispatch,
  deleteDispatch,
  getDispatchById,
  getAllDispatchesForAdmin,
  getAllUsersPublic,
  type DispatchKind,
  type DispatchPollInput,
  type DispatchStatus,
  type DispatchWithAuthor,
} from '../../lib/db';
import { dispatchSlug } from '../../lib/format';
import type { FieldContext, Resource } from '../resource-types';

// ── Helpers ─────────────────────────────────────────────────────────────────

function nowSqlite(): string {
  return new Date().toISOString().slice(0, 19).replace('T', ' ');
}

function plusDaysSqlite(days: number): string {
  return new Date(Date.now() + days * 86_400_000).toISOString().slice(0, 19).replace('T', ' ');
}

/** "YYYY-MM-DD" → "YYYY-MM-DD 23:59:59" (end-of-day in DB local time). */
function endOfDaySqlite(dateStr: string): string {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return '';
  return `${dateStr} 23:59:59`;
}

/** Read pulse_* fields out of FormData. Returns null when no question was provided. */
function extractPulseFromFormData(formData: FormData): DispatchPollInput | null {
  const question = String(formData.get('pulse_question') ?? '').trim();
  if (!question) return null;
  const options = formData
    .getAll('pulse_options')
    .map((v) => String(v).trim())
    .filter(Boolean)
    .slice(0, 4);
  if (options.length < 2) return null;

  // Opens always = now (or the dispatch's publish moment). closes_at comes
  // from a date input and snaps to 23:59:59 on the chosen day; missing →
  // default to 14 days out.
  const opens_at = nowSqlite();
  const closesRaw = String(formData.get('pulse_closes_at') ?? '').trim();
  const closes_at = endOfDaySqlite(closesRaw) || plusDaysSqlite(14);

  return { question, options, opens_at, closes_at };
}

// ── Resource ────────────────────────────────────────────────────────────────

export const dispatchesResource: Resource<DispatchWithAuthor> = {
  key: 'dispatches',
  label: 'Dispatches',
  pluralLabel: 'Dispatches',
  singularLabel: 'Dispatch',
  groupKey: 'publishing',
  description: 'Updates, decisions, notes — the public record of pilot progress.',
  publicRoutePattern: (item) => `/dispatches/${dispatchSlug(item)}`,

  list: {
    queryFn: () => getAllDispatchesForAdmin(),
    columns: [
      {
        key: 'title',
        label: 'Title',
        primary: true,
        width: '2fr',
        render: (item) => ({
          title: item.title,
          subtitle: item.author_name,
        }),
      },
      {
        key: 'kind',
        label: 'Kind',
        kind: 'pill',
        width: '140px',
        pillVariants: {
          decision: { label: 'Decision', class: 'pill-decision' },
          update: { label: 'Update', class: 'pill-update' },
          note: { label: 'Note', class: 'pill-note' },
          behind_the_scenes: { label: 'Behind the scenes', class: 'pill-bts' },
        },
      },
      {
        key: 'status',
        label: 'Status',
        kind: 'pill',
        width: '110px',
        pillVariants: {
          draft: { label: 'Draft', class: 'pill-draft' },
          published: { label: 'Published', class: 'pill-published' },
          archived: { label: 'Archived', class: 'pill-archived' },
        },
      },
      {
        key: 'updated_at',
        label: 'Updated',
        kind: 'relative-date',
        width: '110px',
        emptyFallback: '—',
      },
    ],
    filters: [
      { key: 'all', label: 'All', predicate: () => true, isDefault: true },
      { key: 'published', label: 'Published', predicate: (i) => i.status === 'published' },
      { key: 'drafts', label: 'Drafts', predicate: (i) => i.status === 'draft' },
      { key: 'archived', label: 'Archived', predicate: (i) => i.status === 'archived' },
    ],
    search: {
      placeholder: 'Search by title or body…',
      fields: ['title', 'body'],
    },
    defaultSort: { key: 'updated_at', direction: 'desc' },
    pageSize: 25,
  },

  // Drafts in the sidebar light up terracotta until they're published.
  notifyCount: {
    count: (items) => items.filter((i) => i.status === 'draft').length,
  },

  form: {
    fields: [
      { key: 'title', label: 'Title', kind: 'text', required: true, maxLength: 200 },
      {
        key: 'kind',
        label: 'Kind',
        kind: 'select',
        options: [
          { value: 'decision', label: 'Decision' },
          { value: 'update', label: 'Update' },
          { value: 'note', label: 'Note' },
          { value: 'behind_the_scenes', label: 'Behind the scenes' },
        ],
        defaultValue: 'note',
      },
      {
        key: 'author_id',
        label: 'Author',
        kind: 'select-async',
        required: true,
        loadOptions: () =>
          getAllUsersPublic()
            .filter((u) => u.role === 'fenja')
            .map((u) => ({ value: u.id, label: u.name })),
        defaultValue: (ctx: FieldContext) => ctx.actingUserId,
      },
      {
        key: 'excerpt',
        label: 'Excerpt',
        kind: 'textarea',
        rows: 4,
        helperText:
          'Two to four sentences. The first sentence becomes the lead paragraph on the dispatch banner. Leave blank to fall back to the first ~200 chars of body.',
      },
      {
        key: 'body',
        label: 'Body (markdown)',
        kind: 'markdown',
        rows: 14,
        required: true,
      },
      {
        key: 'status',
        label: 'Status on save',
        kind: 'select',
        options: [
          { value: 'draft', label: 'Draft (hidden from members)' },
          { value: 'published', label: 'Published (visible immediately)' },
          { value: 'archived', label: 'Archived (hidden from members, kept here)' },
        ],
        defaultValue: 'draft',
        helperText:
          'Switching from draft to published is the same as clicking the Publish action — the dispatch becomes visible to all members.',
      },
    ],
    embeds: [{ key: 'pulse', title: 'Pulse', component: 'pulse-sub-form' }],
  },

  ops: {
    getById: (id) => getDispatchById(id),

    create: (data, ctx) => {
      const poll = ctx.formData ? extractPulseFromFormData(ctx.formData) : null;
      const status = (data.status as DispatchStatus) ?? 'draft';
      return createDispatch({
        title: String(data.title),
        body: String(data.body),
        excerpt: ((data.excerpt as string) ?? '').trim() || null,
        kind: data.kind as DispatchKind,
        author_id: Number(data.author_id),
        status,
        poll,
      });
    },

    update: (id, data, ctx) => {
      const poll = ctx.formData ? extractPulseFromFormData(ctx.formData) : null;
      const current = getDispatchById(id);
      if (!current) throw new Error(`Dispatch ${id} not found`);

      updateDispatch(id, {
        title: String(data.title),
        body: String(data.body),
        excerpt: ((data.excerpt as string) ?? '').trim() || null,
        kind: data.kind as DispatchKind,
        author_id: Number(data.author_id),
        poll,
        // Only flag pollExplicit when a question was actually submitted.
        // Empty pulse fields leave the existing pulse alone.
        pollExplicit: poll !== null,
      });

      // Status transitions ride on the save submit.
      const desiredStatus = (data.status as DispatchStatus) ?? current.status;
      if (desiredStatus !== current.status) {
        if (desiredStatus === 'published') publishDispatch(id);
        else if (desiredStatus === 'archived') archiveDispatch(id);
        // 'draft' from another state is a no-op — there's no "unpublish".
      }
    },

    delete: (id) => deleteDispatch(id),
  },

  actions: [
    {
      key: 'publish',
      label: 'Publish now',
      visibleWhen: (item) => item.status === 'draft',
      confirmText: 'Publish this dispatch to all members?',
      handler: (id) => {
        publishDispatch(id);
      },
    },
    {
      key: 'archive',
      label: 'Archive',
      visibleWhen: (item) => item.status === 'published',
      destructive: true,
      confirmText: 'Archive this dispatch? It will be hidden from members.',
      handler: (id) => {
        archiveDispatch(id);
      },
    },
  ],
};
