import { ArrowLeft, ExternalLink, RefreshCw, Save, Send } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useParams } from "react-router";
import {
  Button,
  ConfirmDialog,
  DismissibleAlert,
  LoadingIndicator,
  PageScrollViewport,
  StatusBadge,
  ToggleSwitch,
  hasScope,
  useGuardedNavigate,
  type PlatformRouteContext
} from "@govoplan/core-webui";
import {
  getFormDefinition,
  getFormInstance,
  getFormInstanceEvents,
  getFormInstanceHistory,
  listFormHandoffs,
  startFormHandoff,
  actOnFormHandoff,
  compensateFormHandoff,
  saveFormDraft,
  submitFormInstance,
  type FormDefinition,
  type FormFieldDefinition,
  type FormInstance,
  type FormInstanceEvent,
  type FormHandoff,
  type ValidationResult
} from "../../api/formsRuntime";


export default function FormInstancePage({ settings, auth }: PlatformRouteContext) {
  const { instanceId = "" } = useParams();
  const navigate = useGuardedNavigate();
  const [instance, setInstance] = useState<FormInstance | null>(null);
  const [definition, setDefinition] = useState<FormDefinition | null>(null);
  const [history, setHistory] = useState<FormInstance[]>([]);
  const [events, setEvents] = useState<FormInstanceEvent[]>([]);
  const [handoffs, setHandoffs] = useState<FormHandoff[]>([]);
  const [values, setValues] = useState<Record<string, unknown>>({});
  const [changeReason, setChangeReason] = useState("");
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");
  const [handoffBusy, setHandoffBusy] = useState(false);
  const [handoffKind, setHandoffKind] = useState<"case" | "workflow">("case");
  const [handoffBinding, setHandoffBinding] = useState("");
  const [compensating, setCompensating] = useState<FormHandoff | null>(null);

  const load = useCallback(async (signal?: AbortSignal) => {
    setLoading(true);
    setError("");
    try {
      const nextInstance = await getFormInstance(settings, instanceId, signal);
      const [nextDefinition, nextHistory, nextEvents, nextHandoffs] = await Promise.all([
        getFormDefinition(settings, instanceId, signal),
        getFormInstanceHistory(settings, instanceId, signal),
        getFormInstanceEvents(settings, instanceId, signal),
        listFormHandoffs(settings, instanceId, signal)
      ]);
      setInstance(nextInstance);
      setDefinition(nextDefinition);
      setHistory(nextHistory.revisions);
      setEvents(nextEvents.events);
      setHandoffs(nextHandoffs.handoffs);
      setValues(nextInstance.values);
      setChangeReason("");
    } finally {
      setLoading(false);
    }
  }, [instanceId, settings]);

  useEffect(() => {
    const controller = new AbortController();
    load(controller.signal).catch((reason) => {
      if ((reason as Error).name !== "AbortError") {
        setError(reason instanceof Error ? reason.message : "The Form could not be loaded.");
      }
    });
    return () => controller.abort();
  }, [load]);

  useEffect(() => {
    if (!definition) return;
    if (!definition.handoff_kinds.includes(handoffKind)) {
      setHandoffKind(definition.handoff_kinds.includes("case") ? "case" : "workflow");
    }
  }, [definition, handoffKind]);

  const editable = instance?.status === "started" || instance?.status === "draft";
  const canSave = instance?.status === "draft" && definition?.allow_drafts;
  const changed = useMemo(
    () => Boolean(instance && JSON.stringify(values) !== JSON.stringify(instance.values)),
    [instance, values]
  );
  const diagnostics = useMemo(() => {
    const grouped = new Map<string, ValidationResult[]>();
    for (const item of instance?.validation_results ?? []) {
      const key = item.field ?? "";
      grouped.set(key, [...(grouped.get(key) ?? []), item]);
    }
    return grouped;
  }, [instance]);
  const localized = useMemo(
    () => localizeDefinition(definition),
    [definition]
  );
  const groups = useMemo(
    () => definition ? visibleGroups(definition, values) : [],
    [definition, values]
  );
  const mayHandoff = Boolean(instance && ["submitted", "validated", "needs_review", "accepted"].includes(instance.status) && instance.service_ref);
  const canAdmin = hasScope(auth, "forms_runtime:workspace:admin");

  async function save() {
    if (!instance || !canSave || !changed || !changeReason.trim()) return;
    setSaving(true);
    setError("");
    try {
      await saveFormDraft(settings, instance, values, changeReason.trim());
      await load();
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "The draft could not be saved.");
    } finally {
      setSaving(false);
    }
  }

  async function submit() {
    if (!instance || !editable) return;
    setSaving(true);
    setError("");
    try {
      await submitFormInstance(settings, instance, values);
      await load();
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "The Form could not be submitted.");
    } finally {
      setSaving(false);
    }
  }

  async function startHandoff() {
    if (!instance || !mayHandoff) return;
    setHandoffBusy(true);
    setError("");
    try {
      await startFormHandoff(settings, instance, handoffKind, handoffBinding);
      await load();
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "The handoff could not be started.");
    } finally {
      setHandoffBusy(false);
    }
  }

  async function handoffAction(item: FormHandoff, action: "retry" | "reconcile") {
    setHandoffBusy(true);
    setError("");
    try {
      await actOnFormHandoff(settings, item.instance_id, item.effect_id, action);
      await load();
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "The handoff could not be updated.");
    } finally {
      setHandoffBusy(false);
    }
  }

  async function compensate() {
    if (!compensating) return;
    setHandoffBusy(true);
    setError("");
    try {
      await compensateFormHandoff(settings, compensating.instance_id, compensating.effect_id, "Operator confirmed that no target effect exists.");
      setCompensating(null);
      await load();
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "The handoff could not be compensated.");
    } finally {
      setHandoffBusy(false);
    }
  }

  return (
    <main className="forms-runtime-page">
      <div className="form-instance-shell">
        <div className="form-instance-toolbar">
          <Button onClick={() => navigate("/forms-runtime")}>
            <ArrowLeft size={16} aria-hidden="true" />
            Forms
          </Button>
          {definition && <strong>{localized.title}</strong>}
          {instance && <StatusBadge status={editable ? "active" : "inactive"} label={humanize(instance.status)} />}
        </div>
        <PageScrollViewport className="form-instance-viewport">
          {error &&
            <DismissibleAlert tone="danger" resetKey={error}>
              {error}
            </DismissibleAlert>
          }
          {loading && <LoadingIndicator label="Loading Form" />}
          {!loading && instance && definition &&
            <div className="form-instance-content">
              <section className="form-instance-main">
                <header>
                  <h1>{localized.title}</h1>
                  {localized.description && <p>{localized.description}</p>}
                </header>
                {groups.map((group) =>
                  <section className="form-runtime-section" key={`${group.pageKey}:${group.sectionKey}`}>
                    {(groups.length > 1 || definition.pages?.length) && <header><h2>{localized.sectionTitles[group.sectionKey] ?? group.sectionTitle}</h2>{group.description && <p>{group.description}</p>}</header>}
                    <div className="form-fields">
                      {group.fields.map((field) =>
                        <FormField
                          key={field.key}
                          field={{
                            ...field,
                            label: localized.fieldLabels[field.key] ?? field.label,
                            help_text: localized.fieldHelpTexts[field.key] ?? field.help_text,
                            options: field.options
                          }}
                          optionLabels={localized.optionLabels[field.key] ?? {}}
                          value={values[field.key]}
                          disabled={!editable || saving}
                          diagnostics={diagnostics.get(field.key) ?? []}
                          onChange={(value) => setValues((current) => {
                            const next = { ...current };
                            if (value === undefined || value === "") delete next[field.key];
                            else next[field.key] = value;
                            return next;
                          })}
                        />
                      )}
                    </div>
                  </section>
                )}
                {(instance.attachment_refs.length > 0 || instance.signature_refs.length > 0) &&
                  <div className="form-evidence-summary">
                    <span>{instance.attachment_refs.length} attachments</span>
                    <span>{instance.signature_refs.length} signatures</span>
                  </div>
                }
                {editable &&
                  <div className="form-instance-actions">
                    {canSave &&
                      <label className="form-change-reason">
                        <span>Change reason</span>
                        <input
                          value={changeReason}
                          onChange={(event) => setChangeReason(event.target.value)}
                          disabled={saving}
                        />
                      </label>
                    }
                    {canSave &&
                      <Button
                        onClick={() => void save()}
                        disabled={!changed || !changeReason.trim() || saving}>
                        <Save size={16} aria-hidden="true" />
                        Save draft
                      </Button>
                    }
                    <Button variant="primary" onClick={() => void submit()} disabled={saving}>
                      <Send size={16} aria-hidden="true" />
                      Submit
                    </Button>
                  </div>
                }
                {!editable && instance.receipt_id &&
                  <div className="form-receipt">
                    <span>Submission receipt</span>
                    <code>{instance.receipt_id}</code>
                  </div>
                }
                {!editable && definition.handoff_kinds.length > 0 &&
                  <section className="form-handoffs">
                    <div className="form-handoff-heading">
                      <h2>Case and workflow handoffs</h2>
                      {mayHandoff &&
                        <div className="form-handoff-create">
                          <select value={handoffKind} disabled={handoffBusy} onChange={(event) => setHandoffKind(event.target.value as "case" | "workflow")}>
                            {definition.handoff_kinds.includes("case") && <option value="case">Create Case</option>}
                            {definition.handoff_kinds.includes("workflow") && <option value="workflow">Start Workflow</option>}
                          </select>
                          <input value={handoffBinding} disabled={handoffBusy} onChange={(event) => setHandoffBinding(event.target.value)} placeholder="Target binding (optional)" aria-label="Exact target binding" />
                          <Button variant="primary" disabled={handoffBusy} onClick={() => void startHandoff()}><Send size={15} aria-hidden="true" />Start</Button>
                        </div>
                      }
                    </div>
                    {handoffs.length === 0 && <p className="form-handoff-empty">No handoff has been requested.</p>}
                    <div className="form-handoff-list">
                      {handoffs.map((item) =>
                        <div className="form-handoff-row" key={item.effect_id}>
                          <span><strong>{humanize(item.binding_kind)}</strong><small>{item.binding_reference}</small></span>
                          <StatusBadge status={item.state === "accepted" || item.state === "reconciled" ? "active" : "inactive"} label={humanize(item.state)} />
                          {item.last_error && <span className="form-handoff-error">{item.last_error}</span>}
                          <span className="form-handoff-actions">
                            {item.href && <Button onClick={() => navigate(item.href!)}><ExternalLink size={15} aria-hidden="true" />Open</Button>}
                            {item.state === "rejected" && <Button disabled={handoffBusy} onClick={() => void handoffAction(item, "retry")}><RefreshCw size={15} aria-hidden="true" />Retry</Button>}
                            {item.state === "outcome_unknown" && <Button disabled={handoffBusy} onClick={() => void handoffAction(item, "reconcile")}><RefreshCw size={15} aria-hidden="true" />Reconcile</Button>}
                            {canAdmin && (item.state === "rejected" || item.state === "outcome_unknown") && <Button variant="danger" disabled={handoffBusy} onClick={() => setCompensating(item)}>Compensate</Button>}
                          </span>
                        </div>
                      )}
                    </div>
                  </section>
                }
              </section>
              <aside className="form-instance-aside">
                <section>
                  <h2>Status history</h2>
                  <ol>
                    {events.map((event) =>
                      <li key={event.event_id}>
                        <strong>{humanize(event.status)}</strong>
                        <span>{humanize(event.event_type)}</span>
                        <time>{formatDateTime(event.occurred_at)}</time>
                      </li>
                    )}
                  </ol>
                </section>
                <section>
                  <h2>Revisions</h2>
                  <ol>
                    {history.map((item) =>
                      <li key={item.revision}>
                        <strong>Revision {item.revision}</strong>
                        <span>{item.change_reason}</span>
                        <time>{formatDateTime(item.recorded_at)}</time>
                      </li>
                    )}
                  </ol>
                </section>
              </aside>
            </div>
          }
        </PageScrollViewport>
      </div>
      <ConfirmDialog
        open={Boolean(compensating)}
        title="Compensate handoff"
        message="Confirm only after verifying that no Case or Workflow target exists. This records an administrative recovery decision; it does not delete a remote target."
        confirmLabel="Confirm absent and compensate"
        tone="danger"
        busy={handoffBusy}
        onCancel={() => setCompensating(null)}
        onConfirm={() => void compensate()}
      />
    </main>
  );
}

function FormField({
  field,
  value,
  disabled,
  diagnostics,
  optionLabels,
  onChange
}: {
  field: FormFieldDefinition;
  value: unknown;
  disabled: boolean;
  diagnostics: ValidationResult[];
  optionLabels: Record<string, string>;
  onChange: (value: unknown) => void;
}) {
  const describedBy = diagnostics.length > 0 ? `form-field-${field.key}-messages` : undefined;
  if (field.value_type === "boolean") {
    return (
      <div className="form-field form-field-toggle">
        <ToggleSwitch
          label={`${field.label}${field.required ? " (required)" : ""}`}
          checked={Boolean(value)}
          onChange={onChange}
          disabled={disabled}
          help={field.help_text ?? undefined}
        />
        <FieldMessages id={describedBy} diagnostics={diagnostics} />
      </div>
    );
  }
  return (
    <label className="form-field">
      <span>{field.label}{field.required && <b aria-hidden="true"> *</b>}</span>
      {field.help_text && <small>{field.help_text}</small>}
      {renderInput(field, value, disabled, describedBy, onChange, optionLabels)}
      <FieldMessages id={describedBy} diagnostics={diagnostics} />
    </label>
  );
}

function renderInput(
  field: FormFieldDefinition,
  value: unknown,
  disabled: boolean,
  describedBy: string | undefined,
  onChange: (value: unknown) => void,
  optionLabels: Record<string, string>
) {
  const common = { disabled, required: field.required, "aria-describedby": describedBy };
  if (field.value_type === "multiline_text" || field.value_type === "object" || field.value_type === "list") {
    return (
      <textarea
        {...common}
        rows={field.value_type === "multiline_text" ? 5 : 7}
        value={structuredValue(value)}
        onChange={(event) => onChange(field.value_type === "multiline_text" ? event.target.value : parseStructured(event.target.value))}
      />
    );
  }
  if (field.value_type === "choice") {
    return (
      <select {...common} value={typeof value === "string" ? value : ""} onChange={(event) => onChange(event.target.value)}>
        <option value="">Select</option>
        {field.options.map((option) => <option key={option} value={option}>{optionLabels[option] ?? option}</option>)}
      </select>
    );
  }
  if (field.value_type === "multi_choice") {
    const selected = Array.isArray(value) ? value.map(String) : [];
    return (
      <select
        {...common}
        multiple
        value={selected}
        onChange={(event) => onChange(Array.from(event.target.selectedOptions, (option) => option.value))}>
        {field.options.map((option) => <option key={option} value={option}>{optionLabels[option] ?? option}</option>)}
      </select>
    );
  }
  const type = field.value_type === "integer" || field.value_type === "number"
    ? "number"
    : field.value_type === "email"
      ? "email"
      : field.value_type === "date"
        ? "date"
        : field.value_type === "datetime"
          ? "datetime-local"
          : "text";
  return (
    <input
      {...common}
      type={type}
      step={field.value_type === "integer" ? 1 : field.value_type === "number" ? "any" : undefined}
      min={numericConstraint(field.constraints.minimum)}
      max={numericConstraint(field.constraints.maximum)}
      minLength={numericConstraint(field.constraints.min_length)}
      maxLength={numericConstraint(field.constraints.max_length)}
      value={inputValue(field, value)}
      onChange={(event) => onChange(inputChangeValue(field, event.target.value))}
    />
  );
}

function FieldMessages({ id, diagnostics }: { id?: string; diagnostics: ValidationResult[] }) {
  if (diagnostics.length === 0) return null;
  return (
    <span id={id} className="form-field-messages" aria-live="polite">
      {diagnostics.map((item) => <small key={item.code} data-severity={item.severity}>{item.message}</small>)}
    </span>
  );
}

function inputValue(field: FormFieldDefinition, value: unknown): string | number {
  if (value === undefined || value === null) return "";
  if (field.value_type === "datetime" && typeof value === "string") {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.valueOf())) {
      const local = new Date(parsed.getTime() - parsed.getTimezoneOffset() * 60_000);
      return local.toISOString().slice(0, 16);
    }
  }
  return typeof value === "number" || typeof value === "string" ? value : String(value);
}

function inputChangeValue(field: FormFieldDefinition, value: string): unknown {
  if (!value) return undefined;
  if (field.value_type === "integer") return Number.parseInt(value, 10);
  if (field.value_type === "number") return Number(value);
  if (field.value_type === "datetime") return new Date(value).toISOString();
  return value;
}

function structuredValue(value: unknown): string {
  if (value === undefined || value === null) return "";
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
}

function parseStructured(value: string): unknown {
  if (!value.trim()) return undefined;
  try {
    return JSON.parse(value);
  } catch {
    return value;
  }
}

function numericConstraint(value: unknown): number | undefined {
  return typeof value === "number" ? value : undefined;
}

function visibleGroups(definition: FormDefinition, values: Record<string, unknown>) {
  const fields = new Map(definition.fields.map((field) => [field.key, field]));
  const fieldVisible = (field: FormFieldDefinition) => !field.visibility_condition || evaluateCondition(field.visibility_condition, values);
  if (!definition.pages?.length) {
    return [{
      pageKey: "page",
      sectionKey: "section",
      sectionTitle: definition.title,
      description: definition.description,
      fields: definition.fields.filter(fieldVisible)
    }];
  }
  return definition.pages.flatMap((page) => {
    if (page.visibility_condition && !evaluateCondition(page.visibility_condition, values)) return [];
    return page.sections.flatMap((section) => {
      if (section.visibility_condition && !evaluateCondition(section.visibility_condition, values)) return [];
      return [{
        pageKey: page.key,
        sectionKey: section.key,
        sectionTitle: section.title,
        description: section.description,
        fields: section.field_keys.map((key) => fields.get(key)).filter((field): field is FormFieldDefinition => Boolean(field && fieldVisible(field)))
      }];
    });
  });
}

function evaluateCondition(condition: NonNullable<FormFieldDefinition["visibility_condition"]>, values: Record<string, unknown>): boolean {
  if (condition.kind === "all") return condition.conditions.every((item) => evaluateCondition(item, values));
  if (condition.kind === "any") return condition.conditions.some((item) => evaluateCondition(item, values));
  if (condition.kind === "not") return !evaluateCondition(condition.conditions[0], values);
  const actual = values[condition.field_key];
  const expected = condition.value;
  switch (condition.operator) {
    case "eq": return actual === expected;
    case "neq": return actual !== expected;
    case "is_empty": return emptyValue(actual);
    case "is_not_empty": return !emptyValue(actual);
    case "in": return Array.isArray(expected) && expected.includes(actual);
    case "not_in": return Array.isArray(expected) && !expected.includes(actual);
    case "contains": return typeof actual === "string" ? actual.includes(String(expected ?? "")) : Array.isArray(actual) && actual.includes(expected);
    case "lt": return comparable(actual, expected, (left, right) => left < right);
    case "lte": return comparable(actual, expected, (left, right) => left <= right);
    case "gt": return comparable(actual, expected, (left, right) => left > right);
    case "gte": return comparable(actual, expected, (left, right) => left >= right);
    default: return false;
  }
}

function emptyValue(value: unknown): boolean {
  return value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0);
}

function comparable(left: unknown, right: unknown, compare: (left: number | string, right: number | string) => boolean): boolean {
  if ((typeof left === "number" && typeof right === "number") || (typeof left === "string" && typeof right === "string")) {
    return compare(left, right);
  }
  return false;
}

function localizeDefinition(definition: FormDefinition | null) {
  const canonical = {
    title: definition?.title ?? "",
    description: definition?.description ?? null,
    fieldLabels: {} as Record<string, string>,
    fieldHelpTexts: {} as Record<string, string>,
    optionLabels: {} as Record<string, Record<string, string>>,
    sectionTitles: {} as Record<string, string>
  };
  if (!definition?.localizations?.length) return canonical;
  const browserLocales = navigator.languages.map((value) => value.toLowerCase());
  const localization = definition.localizations.find((item) => browserLocales.some((locale) => locale === item.locale.toLowerCase() || locale.startsWith(`${item.locale.toLowerCase()}-`)))
    ?? definition.localizations.find((item) => item.locale.toLowerCase() === definition.fallback_locale?.toLowerCase());
  if (!localization) return canonical;
  return {
    title: localization.title || canonical.title,
    description: localization.description || canonical.description,
    fieldLabels: localization.field_labels,
    fieldHelpTexts: localization.field_help_texts,
    optionLabels: localization.option_labels,
    sectionTitles: localization.section_titles
  };
}

function formatDateTime(value: string): string {
  return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
}

function humanize(value: string): string {
  return value.replace(/[_:.-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
