'use client';

/**
 * Custom-fields panel for the patient portal — renders admin-defined
 * fields (form_field_defs) for a given (context, recordId) pair and lets
 * the patient edit + save them inline. Adapted from the admin-portal
 * panel; tokens swapped to the patient-portal design language.
 *
 * Each field saves on its own POST so partial saves are possible and a
 * failing field does not block the rest of the form.
 */

import { useCallback, useEffect, useState } from 'react';
import { Check, Loader2, X } from 'lucide-react';
import {
  formFieldsClient,
  FormFieldsClientError,
  type FormFieldContext,
  type FormFieldDef,
  type FormFieldValuePair,
} from '@/lib/form-fields-client';

interface Props {
  context: FormFieldContext;
  recordId: string;
  /** Title for the surrounding panel. Defaults to "Additional information". */
  title?: string;
  /** Optional helper text rendered under the title. */
  description?: string;
}

type SaveState =
  | { kind: 'idle' }
  | { kind: 'saving' }
  | { kind: 'saved' }
  | { kind: 'error'; message: string };

export function CustomFieldsPanel({
  context,
  recordId,
  title = 'Additional information',
  description,
}: Props) {
  const [pairs, setPairs] = useState<FormFieldValuePair[]>([]);
  const [loading, setLoading] = useState(true);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [drafts, setDrafts] = useState<Record<string, unknown>>({});
  const [saveByDef, setSaveByDef] = useState<Record<string, SaveState>>({});

  const load = useCallback(async () => {
    setLoading(true);
    setLoadError(null);
    try {
      const r = await formFieldsClient.getValues(context, recordId);
      setPairs(r);
      const draft: Record<string, unknown> = {};
      for (const p of r) draft[p.def.id] = extractValue(p);
      setDrafts(draft);
    } catch (e) {
      setLoadError(
        e instanceof FormFieldsClientError
          ? e.message
          : 'Failed to load custom fields',
      );
    } finally {
      setLoading(false);
    }
  }, [context, recordId]);

  useEffect(() => {
    void load();
  }, [load]);

  async function save(defId: string) {
    setSaveByDef((s) => ({ ...s, [defId]: { kind: 'saving' } }));
    try {
      await formFieldsClient.writeValue(defId, recordId, drafts[defId]);
      setSaveByDef((s) => ({ ...s, [defId]: { kind: 'saved' } }));
      setTimeout(() => {
        setSaveByDef((s) =>
          s[defId]?.kind === 'saved' ? { ...s, [defId]: { kind: 'idle' } } : s,
        );
      }, 1500);
    } catch (e) {
      setSaveByDef((s) => ({
        ...s,
        [defId]: {
          kind: 'error',
          message:
            e instanceof FormFieldsClientError ? e.message : 'Save failed',
        },
      }));
    }
  }

  if (loading) {
    return <p className="text-sm text-ink-subtle">Loading custom fields…</p>;
  }
  if (loadError) {
    return (
      <div className="rounded-lg border border-error/40 bg-error/10 p-3 text-sm text-error">
        {loadError}
      </div>
    );
  }
  if (pairs.length === 0) {
    // No fields configured — render nothing so the parent page stays clean.
    return null;
  }

  return (
    <section className="space-y-4">
      <div>
        <h3 className="text-sm font-semibold text-ink-muted">{title}</h3>
        {description ? (
          <p className="mt-0.5 text-xs text-ink-subtle">{description}</p>
        ) : null}
      </div>
      <ul className="space-y-4">
        {pairs.map((p) => (
          <li key={p.def.id} className="space-y-1.5">
            <label className="block text-sm font-medium text-ink">
              {p.def.label}
              {p.def.required ? (
                <span className="ml-0.5 text-error">*</span>
              ) : null}
            </label>
            {p.def.helpText ? (
              <p className="text-xs text-ink-subtle">{p.def.helpText}</p>
            ) : null}
            <div className="flex items-start gap-2">
              <div className="flex-1">
                <FieldInput
                  def={p.def}
                  value={drafts[p.def.id]}
                  onChange={(v) =>
                    setDrafts((d) => ({ ...d, [p.def.id]: v }))
                  }
                  recordId={recordId}
                />
              </div>
              <button
                type="button"
                onClick={() => void save(p.def.id)}
                disabled={saveByDef[p.def.id]?.kind === 'saving'}
                className="inline-flex h-12 items-center gap-1.5 rounded-lg border border-surface-border bg-white px-3 text-sm font-semibold text-ink hover:border-brand disabled:opacity-50"
              >
                {saveByDef[p.def.id]?.kind === 'saving' ? (
                  <Loader2 className="h-3.5 w-3.5 animate-spin" />
                ) : saveByDef[p.def.id]?.kind === 'saved' ? (
                  <Check className="h-3.5 w-3.5 text-green-600" />
                ) : saveByDef[p.def.id]?.kind === 'error' ? (
                  <X className="h-3.5 w-3.5 text-error" />
                ) : null}
                Save
              </button>
            </div>
            {saveByDef[p.def.id]?.kind === 'error' ? (
              <p className="text-xs text-error">
                {
                  (saveByDef[p.def.id] as {
                    kind: 'error';
                    message: string;
                  }).message
                }
              </p>
            ) : null}
          </li>
        ))}
      </ul>
    </section>
  );
}

function extractValue(p: FormFieldValuePair): unknown {
  if (!p.value) return defaultFor(p.def.fieldType);
  switch (p.def.fieldType) {
    case 'short_text':
    case 'long_text':
    case 'url':
    case 'phone':
    case 'select_single':
      return p.value.valueText ?? '';
    case 'number':
      return p.value.valueNumber ?? '';
    case 'date':
      return p.value.valueDate ? String(p.value.valueDate).slice(0, 10) : '';
    case 'boolean':
      return p.value.valueBool ?? false;
    case 'select_multi':
      return Array.isArray(p.value.valueJson) ? p.value.valueJson : [];
    case 'file':
    case 'image':
      return p.value.fileUrl ?? '';
    default:
      return '';
  }
}

function defaultFor(t: string): unknown {
  if (t === 'boolean') return false;
  if (t === 'select_multi') return [];
  return '';
}

const inputBase =
  'h-12 w-full rounded-lg border border-surface-border bg-white px-4 text-sm text-ink focus:border-brand focus:outline-none focus:ring-2 focus:ring-brand/30';

function FieldInput({
  def,
  value,
  onChange,
  recordId,
}: {
  def: FormFieldDef;
  value: unknown;
  onChange: (v: unknown) => void;
  recordId: string;
}) {
  void recordId;
  const validation = (def.validationJson ?? {}) as {
    min?: number;
    max?: number;
  };
  switch (def.fieldType) {
    case 'long_text':
      return (
        <textarea
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={`${inputBase} h-24 py-2`}
        />
      );
    case 'number':
      return (
        <input
          type="number"
          min={validation.min}
          max={validation.max}
          value={String(value ?? '')}
          onChange={(e) =>
            onChange(e.target.value === '' ? '' : Number(e.target.value))
          }
          className={inputBase}
        />
      );
    case 'date':
      return (
        <input
          type="date"
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={inputBase}
        />
      );
    case 'boolean':
      return (
        <label className="inline-flex items-center gap-2 text-sm text-ink">
          <input
            type="checkbox"
            checked={Boolean(value)}
            onChange={(e) => onChange(e.target.checked)}
            className="h-4 w-4 rounded border-surface-border text-brand focus:ring-brand"
          />
          {value ? 'Yes' : 'No'}
        </label>
      );
    case 'select_single':
      return (
        <select
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={inputBase}
        >
          <option value="">— Select —</option>
          {(def.optionsJson ?? []).map((o) => (
            <option key={o.value} value={o.value}>
              {o.label}
            </option>
          ))}
        </select>
      );
    case 'select_multi': {
      const sel = new Set(Array.isArray(value) ? (value as string[]) : []);
      return (
        <div className="flex flex-wrap gap-2">
          {(def.optionsJson ?? []).map((o) => (
            <label
              key={o.value}
              className="inline-flex items-center gap-1.5 rounded-lg border border-surface-border bg-white px-3 py-2 text-sm text-ink"
            >
              <input
                type="checkbox"
                checked={sel.has(o.value)}
                onChange={(e) => {
                  const next = new Set(sel);
                  if (e.target.checked) next.add(o.value);
                  else next.delete(o.value);
                  onChange(Array.from(next));
                }}
                className="h-4 w-4 rounded border-surface-border text-brand focus:ring-brand"
              />
              {o.label}
            </label>
          ))}
        </div>
      );
    }
    case 'url':
      return (
        <input
          type="url"
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={inputBase}
        />
      );
    case 'phone':
      return (
        <input
          type="tel"
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={inputBase}
        />
      );
    case 'file':
    case 'image':
      return (
        <FileInput
          def={def}
          value={value}
          onChange={onChange}
          recordId={recordId}
        />
      );
    case 'short_text':
    default:
      return (
        <input
          type="text"
          value={String(value ?? '')}
          onChange={(e) => onChange(e.target.value)}
          className={inputBase}
        />
      );
  }
}

function FileInput({
  def,
  value,
  onChange,
  recordId,
}: {
  def: FormFieldDef;
  value: unknown;
  onChange: (v: unknown) => void;
  recordId: string;
}) {
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const url = typeof value === 'string' && value.length > 0 ? value : null;
  const isImage = def.fieldType === 'image';

  async function handleFile(file: File) {
    setUploading(true);
    setError(null);
    try {
      const saved = await formFieldsClient.uploadFile(def.id, recordId, file);
      onChange(saved.fileUrl);
    } catch (e) {
      setError(
        e instanceof FormFieldsClientError ? e.message : 'Upload failed',
      );
    } finally {
      setUploading(false);
    }
  }

  return (
    <div className="flex w-full flex-col gap-1.5">
      <div className="flex items-center gap-2">
        <input
          type="file"
          accept={isImage ? 'image/*' : undefined}
          disabled={uploading}
          onChange={(e) => {
            const f = e.target.files?.[0];
            if (f) void handleFile(f);
          }}
          className="text-sm text-ink"
        />
        {uploading ? (
          <span className="text-xs text-ink-subtle">Uploading…</span>
        ) : null}
      </div>
      {url ? (
        isImage ? (
          // eslint-disable-next-line @next/next/no-img-element
          <img
            src={url}
            alt={def.label}
            className="mt-1 h-20 w-20 rounded-lg border border-surface-border object-cover"
          />
        ) : (
          <a
            href={url}
            target="_blank"
            rel="noreferrer"
            className="text-xs font-semibold text-brand hover:underline"
          >
            View uploaded file
          </a>
        )
      ) : null}
      {error ? <p className="text-xs text-error">{error}</p> : null}
    </div>
  );
}
