---
/* ---------------------------------------------------------------------------
 * /admin/<resource> — the production dynamic admin route.
 *
 * Resolves the resource from the URL segment, gates on user.role === 'fenja',
 * dispatches POSTs (save / delete / action key) through validateForResource
 * and resource.ops, redirects with a ?msg=<key> flash on success.
 *
 * 404s when the resource key is not registered — step 8 onward populates
 * the registry; until then most resource keys won't resolve.
 * ------------------------------------------------------------------------- */

import AdminLayout from '../../admin/components/AdminLayout.astro';
import ResourceListView from '../../admin/components/ResourceListView.astro';
import ResourceEditPanel from '../../admin/components/ResourceEditPanel.astro';
import { groups } from '../../admin/resources';
import { validateForResource, type ValidationErrors } from '../../admin/validate';
import type {
  ActionResult,
  Field,
  OpContext,
  Resource,
} from '../../admin/resource-types';

// ── Auth gate ─────────────────────────────────────────────────────────────
const user = Astro.locals.user;
if (user.role !== 'fenja') return Astro.redirect('/');

// ── Resolve resource from URL segment ─────────────────────────────────────
const resourceKey = Astro.params.resource;
const allResources = groups.flatMap((g) => g.resources);
const resource = allResources.find((r) => r.key === resourceKey) as
  | Resource
  | undefined;

if (!resource) {
  return new Response('Resource not found', { status: 404 });
}

const resourceBase = `/admin/${resource.key}`;
const opCtx: OpContext = {
  user: { id: user.id, role: user.role },
  origin: Astro.url.origin,
};

// ── Form-data → typed record (driven by the field configs) ────────────────
function parseFormData(
  formData: FormData,
  fields: Field[],
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const field of fields) {
    if (field.readOnly) continue;

    switch (field.kind) {
      case 'multi-text': {
        out[field.key] = formData
          .getAll(field.key)
          .map((v) => String(v))
          .filter((v) => v.trim() !== '');
        break;
      }
      case 'multi-select-async': {
        out[field.key] = formData.getAll(field.key).map((v) => {
          const s = String(v);
          const n = Number(s);
          return Number.isFinite(n) && s !== '' ? n : s;
        });
        break;
      }
      case 'number': {
        const v = formData.get(field.key);
        if (v == null || v === '') {
          out[field.key] = null;
        } else {
          const n = Number(v);
          out[field.key] = Number.isFinite(n) ? n : v;
        }
        break;
      }
      default: {
        const v = formData.get(field.key);
        out[field.key] = v == null ? '' : String(v);
      }
    }
  }
  return out;
}

// ── State that survives a failed POST (so the panel re-fills) ─────────────
let errors: ValidationErrors = {};
let formError: string | null = null;
let resubmitValues: Record<string, unknown> | null = null;

// ── POST dispatch ────────────────────────────────────────────────────────
if (Astro.request.method === 'POST') {
  const formData = await Astro.request.formData();
  const action = String(formData.get('_action') ?? 'save');
  opCtx.formData = formData;

  const editIdParam = Astro.url.searchParams.get('edit');
  const editId =
    editIdParam && Number.isFinite(Number(editIdParam))
      ? Number(editIdParam)
      : null;

  // ── save ──────────────────────────────────────────────────────────────
  if (action === 'save') {
    if (!resource.form) {
      return new Response('Resource is read-only', { status: 403 });
    }

    const data = parseFormData(formData, resource.form.fields);
    const existingItem =
      editId !== null && resource.ops.getById
        ? ((await resource.ops.getById(editId)) as Record<string, unknown> | null)
        : null;

    errors = validateForResource({
      resource,
      data,
      item: existingItem,
      actingUserId: user.id,
    });

    if (Object.keys(errors).length === 0) {
      try {
        if (editId !== null && resource.ops.update) {
          await resource.ops.update(editId, data, opCtx);
          const extra = resultRedirectParam(opCtx.result);
          return Astro.redirect(`${resourceBase}?edit=${editId}&msg=saved${extra}`);
        }
        if (editId === null && resource.ops.create) {
          const newId = await resource.ops.create(data, opCtx);
          const extra = resultRedirectParam(opCtx.result);
          return Astro.redirect(`${resourceBase}?edit=${newId}&msg=created${extra}`);
        }
        return Astro.redirect(`${resourceBase}?msg=saved`);
      } catch (err) {
        formError = err instanceof Error ? err.message : 'Save failed';
        resubmitValues = data;
      }
    } else {
      resubmitValues = data;
    }
  }

  // ── delete ────────────────────────────────────────────────────────────
  else if (action === 'delete') {
    if (editId !== null && resource.ops.delete) {
      try {
        await resource.ops.delete(editId, opCtx);
        return Astro.redirect(`${resourceBase}?msg=deleted`);
      } catch (err) {
        formError = err instanceof Error ? err.message : 'Delete failed';
      }
    }
  }

  // ── custom action ─────────────────────────────────────────────────────
  else {
    const customAction = resource.actions?.find((a) => a.key === action);
    if (customAction && editId !== null) {
      try {
        const direct = await customAction.handler(editId, opCtx);
        // Handlers may set ctx.result or return an ActionResult — accept both.
        const result = (direct as ActionResult | undefined) ?? opCtx.result;
        const extra = resultRedirectParam(result);
        // Some actions remove the item entirely (e.g. decline). Land on the
        // list view in that case so we don't 404 trying to re-fetch the row.
        const stillExists = resource.ops.getById
          ? (await resource.ops.getById(editId)) !== null
          : true;
        const target = stillExists
          ? `${resourceBase}?edit=${editId}&msg=action_${encodeURIComponent(action)}${extra}`
          : `${resourceBase}?msg=action_${encodeURIComponent(action)}${extra}`;
        return Astro.redirect(target);
      } catch (err) {
        formError = err instanceof Error ? err.message : 'Action failed';
      }
    } else {
      return new Response('Unknown action', { status: 400 });
    }
  }
}

function resultRedirectParam(r: ActionResult | undefined): string {
  if (!r) return '';
  if (r.kind === 'invite-link') {
    return `&invite_url=${encodeURIComponent(r.url)}`;
  }
  return '';
}

// ── GET / failed-POST render ──────────────────────────────────────────────
const isNew = Astro.url.searchParams.get('new') === '1';
const editIdRaw = Astro.url.searchParams.get('edit');
const editId =
  editIdRaw && Number.isFinite(Number(editIdRaw)) ? Number(editIdRaw) : null;

const editingItem =
  editId !== null && resource.ops.getById
    ? ((await resource.ops.getById(editId)) as Record<string, unknown> | null)
    : null;

// Panel renders when:
// - editing/creating a form-bearing resource, OR
// - reviewing an item from a form-null resource that has a summary (e.g. join_requests)
const showPanel = resource.form !== null
  ? (isNew || editingItem !== null)
  : (editingItem !== null && resource.summary !== undefined);

const msg = Astro.url.searchParams.get('msg');
const pageTitle = `${resource.pluralLabel} — Backstage`;

// Friendly flash text. Anything action_<key> is rendered as
// "<action.label> done." using the resource's action label.
function flashTextFor(rawMsg: string | null): string | null {
  if (!rawMsg) return null;
  if (formError) return formError;
  if (rawMsg.startsWith('action_')) {
    const key = rawMsg.slice('action_'.length);
    const action = resource!.actions?.find((a) => a.key === key);
    return action ? `${action.label}.` : null;
  }
  return ({
    saved: 'Saved.',
    created: 'Created.',
    deleted: 'Deleted.',
  } as Record<string, string>)[rawMsg] ?? null;
}
const flash = formError ?? flashTextFor(msg);
const flashKind = formError ? 'error' : 'success';
---

<AdminLayout
  title={pageTitle}
  groups={groups}
  activeResourceKey={resource.key}
>
  {flash && (
    <div class:list={['bs-flash', flashKind]} role="status">{flash}</div>
  )}

  <ResourceListView resource={resource} groups={groups} />

  {showPanel && (
    <ResourceEditPanel
      resource={resource}
      item={editingItem}
      formValues={resubmitValues ?? undefined}
      errors={errors}
      actingUserId={user.id}
    />
  )}
</AdminLayout>
