<script lang="ts">
  import {
    ErrorBoundary,
    FormerResolveSpecAPI,
    GridlerFull,
    NativeSelectCtrl,
    type GridColumnFilters,
    type GridlerColumn,
    type GridlerContextMenuItem,
  } from "@warkypublic/svelix";
  import FormerShell from "../shared/FormerShell.svelte";
  import { onMount } from "svelte";
  import { api } from "../../api";
  import { adminGridTheme } from "../../gridTheme";
  import { GlobalStateStore } from "../../shellState";
  import type { StoredFile, Thought, ThoughtLink } from "../../types";
  import ContentEditorField from "../shared/ContentEditorField.svelte";

  type ThoughtForm = { id?: string; content: string; project_id?: string };

  const THOUGHT_PRIMARY_KEY = 'id';

  let includeArchived = $state(false);
  let actionBusy = $state<string | null>(null);
  let actionError = $state("");
  let inspectorBusy = $state(false);
  let selectedThought = $state<Thought | null>(null);
  let relatedLinks = $state<ThoughtLink[]>([]);
  let relatedFiles = $state<StoredFile[]>([]);
  let formOpened = $state(false);
  let formRequest = $state<'insert' | 'update' | 'delete'>('insert');
  let formValues = $state<ThoughtForm>({ content: '' });
  let editorOpened = $state(false);
  let editorValues = $state<{ id?: string; content: string }>({ content: '' });
  let contextRow = $state<Record<string, unknown> | null>(null);
  let refreshKey = $state(0);
  let projectOptions = $state<{ label: string; value: string }[]>([]);
  const authToken = GlobalStateStore.getState().session.authToken ?? '';
  const thoughtOnAPICall = $derived(FormerResolveSpecAPI({
    authToken,
    url: '/api/rs/public/thoughts'
  }));

  onMount(async () => {
    const projects = await api.projects.list();
    projectOptions = projects.map((p) => ({ label: p.name, value: p.id }));
  });

  const menuItems: GridlerContextMenuItem[] = [
    { id: 'add', label: 'Add' },
    { id: 'edit', label: 'Edit' },
    { id: 'edit_content', label: 'Edit Content' },
    { id: 'delete', label: 'Delete' },
  ];

  function onRowContextMenu(_row: number, rowData: Record<string, unknown> | undefined) {
    contextRow = rowData ?? null;
  }

  function normalizeThoughtRecordForFormer(data: Record<string, unknown>): ThoughtForm {
    return {
      id: data.id != null ? String(data.id) : undefined,
      content: typeof data.content === 'string' ? data.content : '',
      project_id: typeof data.project_id === 'string' && data.project_id ? data.project_id : undefined,
    };
  }

  async function loadThoughtFromRow(rowData: Record<string, unknown>): Promise<Thought> {
    const id = String(rowData[THOUGHT_PRIMARY_KEY] ?? '');
    return await thoughtOnAPICall('read', 'update', undefined, id) as Thought;
  }

  async function onMenuItemSelect(item: GridlerContextMenuItem) {
    if (item.id === 'add') {
      formValues = { content: '' };
      formRequest = 'insert';
      formOpened = true;
      return;
    }
    if (!contextRow) return;
    if (item.id === 'edit_content') {
      const thought = normalizeThought(contextRow);
      selectedThought = thought;
      editorValues = { id: thought.id, content: thought.content };
      editorOpened = true;
      return;
    }
    const thought = normalizeThought(contextRow);
    formValues = {
      id: thought.id,
      content: thought.content,
      project_id: thought.project_id
    };
    formRequest = item.id === 'delete' ? 'delete' : 'update';
    formOpened = true;
  }

  function normalizeThoughtForm(data: ThoughtForm): Record<string, unknown> {
    return {
      content: data.content,
      project_id: data.project_id || undefined
    };
  }

  async function handleThoughtSaved() {
    formOpened = false;
    if (contextRow?.id) {
      const thought = await loadThoughtFromRow(contextRow);
      await inspectThought(thought);
    }
    refreshKey++;
  }

  async function handleThoughtEditorSaved() {
    editorOpened = false;
    if (editorValues.id) {
      const thought = await thoughtOnAPICall('read', 'update', undefined, editorValues.id) as Thought;
      editorValues = { id: thought.id, content: thought.content };
      await inspectThought(thought);
    }
    refreshKey++;
  }
  let gridTotal = $state<number | null>(null);
  const thoughtsDataSourceOptions = {
    url: "/api/rs",
    authToken: GlobalStateStore.getState().session.authToken,
    schema: "public",
    entity: "thoughts",
    uniqueID: THOUGHT_PRIMARY_KEY,
    hotfields: [THOUGHT_PRIMARY_KEY, "guid", "metadata", "project_id", "archived_at"],
    sort: [{ column: "created_at", direction: "desc" }],
  } as unknown as {
    url: string;
    authToken?: string;
    schema: string;
    entity: string;
    uniqueID: string;
    hotfields: string[];
  };

  const columns: GridlerColumn[] = [
    { id: "id", title: "ID", dataKey: "id", width: 90, format: "number" },
    { id: "content", title: "Content", dataKey: "content", width: 520 },
    {
      id: "created_at",
      title: "Created",
      dataKey: "created_at",
      width: 210,
      format: "datetime",
    },
    {
      id: "updated_at",
      title: "Updated",
      dataKey: "updated_at",
      width: 210,
      format: "datetime",
    },
    {
      id: "archived_at",
      title: "Archived",
      dataKey: "archived_at",
      width: 210,
      format: "datetime",
    },
  ];

  const baseFilters = $derived<GridColumnFilters>(
    includeArchived
      ? {}
      : {
          archived_at: {
            value: "",
            op: "isNull",
          },
        },
  );

  async function archive(id: string) {
    actionBusy = id;
    actionError = "";
    try {
      await api.thoughts.archive(id);
      if (selectedThought?.id === id)
        selectedThought.archived_at = new Date().toISOString();
    } catch (e) {
      actionError = e instanceof Error ? e.message : "Archive failed";
    } finally {
      actionBusy = null;
    }
  }

  async function remove(id: string) {
    if (!confirm("Permanently delete this thought?")) return;
    actionBusy = id;
    actionError = "";
    try {
      await api.thoughts.delete(id);
      if (selectedThought?.id === id) {
        selectedThought = null;
        relatedLinks = [];
        relatedFiles = [];
      }
    } catch (e) {
      actionError = e instanceof Error ? e.message : "Delete failed";
    } finally {
      actionBusy = null;
    }
  }

  function normalizeThought(rowData: Record<string, unknown>): Thought {
    const metadataObj = (rowData.metadata ?? {}) as Record<string, unknown>;
    return {
      id: String(rowData.id ?? ""),
      guid: rowData.guid ? String(rowData.guid) : undefined,
      content: typeof rowData.content === "string" ? rowData.content : "",
      project_id: rowData.project_id ? String(rowData.project_id) : undefined,
      archived_at: rowData.archived_at
        ? String(rowData.archived_at)
        : undefined,
      created_at: String(rowData.created_at ?? ""),
      updated_at: String(rowData.updated_at ?? ""),
      metadata: {
        people: Array.isArray(metadataObj.people)
          ? (metadataObj.people as string[])
          : [],
        action_items: Array.isArray(metadataObj.action_items)
          ? (metadataObj.action_items as string[])
          : [],
        dates_mentioned: Array.isArray(metadataObj.dates_mentioned)
          ? (metadataObj.dates_mentioned as string[])
          : [],
        topics: Array.isArray(metadataObj.topics)
          ? (metadataObj.topics as string[])
          : [],
        type: typeof metadataObj.type === "string" ? metadataObj.type : "",
        source:
          typeof metadataObj.source === "string" ? metadataObj.source : "",
        metadata_status:
          typeof metadataObj.metadata_status === "string"
            ? metadataObj.metadata_status
            : "",
        metadata_error:
          typeof metadataObj.metadata_error === "string"
            ? metadataObj.metadata_error
            : undefined,
        attachments: Array.isArray(metadataObj.attachments)
          ? (metadataObj.attachments as Thought["metadata"]["attachments"])
          : [],
      },
    };
  }

  async function inspectThought(row: Thought | null) {
    selectedThought = row;
    relatedLinks = [];
    relatedFiles = [];
    if (!row) return;

    inspectorBusy = true;
    try {
      const thoughtRef = row.guid ?? row.id;
      const [links, files] = await Promise.all([
        api.thoughts.links(row.id),
        api.files.list({ thought_id: thoughtRef }),
      ]);
      relatedLinks = links;
      relatedFiles = files;
    } finally {
      inspectorBusy = false;
    }
  }

  function onRowClick(
    _row: number,
    rowData: Record<string, unknown> | undefined,
  ) {
    if (!rowData) {
      void inspectThought(null);
      return;
    }
    void inspectThought(normalizeThought(rowData));
  }

  function onRowDblClick(_row: number, rowData: Record<string, unknown> | undefined) {
    if (!rowData) return;
    contextRow = rowData;
    void onMenuItemSelect({ id: 'edit', label: 'Edit' });
  }

  function onGridEvent(
    type: string,
    _item?: unknown,
    _column?: unknown,
    _coords?: unknown,
    detail?: Record<string, unknown>,
  ) {
    if (type !== "page_loaded" && type !== "load") return;
    const total = detail?.total;
    if (typeof total === "number") gridTotal = total;
  }

  function formatDate(value?: string) {
    if (!value) return "—";
    return new Date(value).toLocaleString();
  }

  function isSelectedArchived(): boolean {
    return !!selectedThought?.archived_at;
  }
</script>

<div class="space-y-4 w-full">
  <div class="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
    <div>
      <h2 class="text-2xl font-semibold text-white">Thoughts</h2>
      <p class="mt-1 text-sm text-slate-400">
        {#if gridTotal === null}
          Server-backed grid
        {:else}
          {gridTotal} result{gridTotal !== 1 ? "s" : ""}
        {/if}
      </p>
    </div>
    <div class="flex items-center gap-3">
      <label class="flex items-center gap-2 text-sm text-slate-400">
        <input
          type="checkbox"
          class="accent-cyan-400"
          bind:checked={includeArchived}
        />
        Archived
      </label>
      <button
        class="rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm text-slate-200 transition hover:bg-white/10"
        onclick={() => { formValues = { content: '' }; formRequest = 'insert'; formOpened = true; }}>New Thought</button
      >
      <button
        class="rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm text-slate-200 transition hover:bg-white/10"
        onclick={() => {
          selectedThought = null;
          relatedLinks = [];
          relatedFiles = [];
        }}>Refresh</button
      >
    </div>
  </div>

  {#if actionError}
    <p class="text-sm text-rose-300">{actionError}</p>
  {/if}

  <div class="flex flex-col gap-4">
    <div class="rounded-2xl border border-white/10 bg-slate-950/30 p-3">
      {#key refreshKey}
        <ErrorBoundary namespace="ThoughtsGridlerFull">
          <GridlerFull
            {columns}
            theme={adminGridTheme}
            rowMarkers="number"
            height={560}
            width="100%"
            pageSize={40}
            dataSource="resolvespec"
            dataSourceOptions={thoughtsDataSourceOptions}
            serverSideSearch={true}
            searchColumns={["content"]}
            filters={baseFilters}
            {menuItems}
            {onGridEvent}
            {onRowClick}
            {onRowDblClick}
            {onRowContextMenu}
            {onMenuItemSelect}
          />
        </ErrorBoundary>
      {/key}
    </div>

    <aside class="rounded-2xl border border-white/10 bg-slate-900/70 p-4">
      <div class="flex items-start justify-between gap-3">
        <h3 class="text-sm font-semibold text-white">Thought Inspector</h3>
        {#if selectedThought}
          <div class="flex gap-2">
            <button
              class="text-xs text-cyan-300 hover:text-cyan-200"
              onclick={() => {
                const thought = selectedThought;
                if (!thought) return;
                editorValues = { id: thought.id, content: thought.content };
                editorOpened = true;
              }}>Edit Content</button
            >
            {#if !isSelectedArchived()}
              <button
                class="text-xs text-slate-300 hover:text-white"
                onclick={() => {
                  if (selectedThought) void archive(selectedThought.id);
                }}
                disabled={actionBusy === selectedThought?.id}>Archive</button
              >
            {/if}
            <button
              class="text-xs text-rose-300 hover:text-rose-200"
              onclick={() => {
                if (selectedThought) void remove(selectedThought.id);
              }}
              disabled={actionBusy === selectedThought?.id}>Delete</button
            >
          </div>
        {/if}
      </div>

      {#if !selectedThought}
        <p class="mt-3 text-sm text-slate-500">
          Select a thought row to inspect metadata, links, and file records.
        </p>
      {:else}
        <div class="mt-3 space-y-3 text-sm text-slate-300">
          <p class="text-slate-200">{selectedThought.content}</p>
          <div class="rounded-xl border border-white/10 bg-white/5 p-3">
            <p>
              <strong class="text-slate-100">Type:</strong>
              {selectedThought.metadata.type || "—"}
            </p>
            <p>
              <strong class="text-slate-100">Status:</strong>
              {selectedThought.metadata.metadata_status || "active"}
            </p>
            {#if selectedThought.metadata.metadata_error}
              <p class="mt-1 text-rose-300">
                <strong>Error:</strong>
                {selectedThought.metadata.metadata_error}
              </p>
            {/if}
            <p class="mt-1">
              <strong class="text-slate-100">Created:</strong>
              {formatDate(selectedThought.created_at)}
            </p>
            <p>
              <strong class="text-slate-100">Updated:</strong>
              {formatDate(selectedThought.updated_at)}
            </p>
          </div>

          <div>
            <p class="text-xs uppercase tracking-[0.18em] text-slate-500">
              Related Links ({relatedLinks.length})
            </p>
            {#if inspectorBusy}
              <p class="mt-2 text-xs text-slate-500">Loading links…</p>
            {:else if relatedLinks.length === 0}
              <p class="mt-2 text-xs text-slate-500">No related links.</p>
            {:else}
              <ul class="mt-2 space-y-1 text-xs">
                {#each relatedLinks as link}
                  <li
                    class="rounded-lg border border-white/10 bg-white/5 px-2 py-1"
                  >
                    <code class="text-cyan-100">{link.from_id}</code> →
                    <code class="text-cyan-100">{link.to_id}</code>
                    · {link.relation}
                  </li>
                {/each}
              </ul>
            {/if}
          </div>

          <div>
            <p class="text-xs uppercase tracking-[0.18em] text-slate-500">
              Attachments ({selectedThought.metadata.attachments?.length ?? 0})
            </p>
            {#if (selectedThought.metadata.attachments?.length ?? 0) === 0}
              <p class="mt-2 text-xs text-slate-500">No attachment metadata.</p>
            {:else}
              <ul class="mt-2 space-y-1 text-xs">
                {#each selectedThought.metadata.attachments ?? [] as attachment}
                  <li
                    class="rounded-lg border border-white/10 bg-white/5 px-2 py-1"
                  >
                    {attachment.name} · {attachment.media_type} · {attachment.size_bytes}
                    bytes
                  </li>
                {/each}
              </ul>
            {/if}
          </div>

          <div>
            <p class="text-xs uppercase tracking-[0.18em] text-slate-500">
              Stored File Records ({relatedFiles.length})
            </p>
            {#if inspectorBusy}
              <p class="mt-2 text-xs text-slate-500">Loading files…</p>
            {:else if relatedFiles.length === 0}
              <p class="mt-2 text-xs text-slate-500">
                No stored files linked to this thought.
              </p>
            {:else}
              <ul class="mt-2 space-y-1 text-xs">
                {#each relatedFiles as file}
                  <li
                    class="rounded-lg border border-white/10 bg-white/5 px-2 py-1"
                  >
                    <a
                      href={`/files/${file.id}`}
                      target="_blank"
                      rel="noreferrer"
                      class="text-cyan-300 hover:text-cyan-200">{file.name}</a
                    >
                    · {file.media_type}
                  </li>
                {/each}
              </ul>
            {/if}
          </div>
        </div>
      {/if}
    </aside>
  </div>
</div>

<ErrorBoundary namespace="ThoughtsEditorFormer">
  <FormerShell
    bind:opened={editorOpened}
    bind:values={editorValues}
    request="update"
    title="Edit Thought"
    uniqueKeyField={THOUGHT_PRIMARY_KEY}
    width="min(96vw, 90rem)"
    onAPICall={thoughtOnAPICall}
    beforeSave={(data) => ({ content: data.content })}
    afterSave={handleThoughtEditorSaved}
    onClose={() => { editorOpened = false; }}
  >
    {#snippet children(state)}
      <ContentEditorField
        filename="thought.md"
        value={state.values?.content ?? ''}
        onchange={(v) => state.setState('values', { ...state.values, content: v })}
      />
    {/snippet}
  </FormerShell>
</ErrorBoundary>

<ErrorBoundary namespace="ThoughtsFormer">
<FormerShell
  bind:opened={formOpened}
  bind:values={formValues}
  bind:request={formRequest}
  title={formRequest === 'insert' ? 'New Thought' : formRequest === 'update' ? 'Edit Thought' : 'Delete Thought'}
  uniqueKeyField={THOUGHT_PRIMARY_KEY}
  onAPICall={thoughtOnAPICall}
  afterGet={async (data) => normalizeThoughtRecordForFormer(data as Record<string, unknown>)}
  beforeSave={normalizeThoughtForm}
  afterSave={handleThoughtSaved}
  onClose={() => { formOpened = false; }}
>
  {#snippet children(state)}
    <div class="space-y-4 p-4">
      <ContentEditorField
        filename="thought.md"
        value={state.values?.content ?? ''}
        disabled={state.request === 'delete'}
        onchange={(v) => state.setState('values', { ...state.values, content: v })}
      />
      <NativeSelectCtrl
        label="Project"
        name="project_id"
        disabled={state.request === 'delete'}
        value={state.values?.project_id ?? ''}
        options={[{ label: '— None —', value: '' }, ...projectOptions]}
        onchange={(v) => state.setState('values', { ...state.values, project_id: v || undefined })}
      />
    </div>
  {/snippet}
</FormerShell>
</ErrorBoundary>
