/**
 * FormRenderer — evidence-backed attributes (CE-WP-0010/0012).
 */

import {
  useMemo,
  useRef,
  useState,
  type ChangeEvent,
  type CSSProperties,
} from "react";

import type { EvidenceTarget } from "@citation-evidence/engine/shared";

import {
  DEFAULT_CURRENCY,
  displayTypeLabel,
  formatOptionsText,
  normalizeAttributeType,
  parseAmountValue,
  parseOptionsText,
  parseSomeOfValue,
  serializeAmountValue,
  serializeSomeOfValue,
  typeNeedsOptions,
  type AttributeOption,
  type FormFieldSchema,
  type FormSchema,
} from "./attribute-types";
import { FieldDefinitionForm, type FieldType } from "./FieldDefinitionForm";
import { useActiveState, type ActiveState } from "./state/active";
import { useRegisterRect } from "./visual-guide/react-hooks";

export type {
  AttributeOption,
  AttributeValueType,
  FormFieldSchema,
  FormSchema,
} from "./attribute-types";
export {
  isAttributeValueType,
  normalizeFormSchema,
  normalizeAttributeType,
  parseAmountValue,
  serializeAmountValue,
} from "./attribute-types";

const FILTER_MIN_CHARS = 3;

function isFieldActive(state: ActiveState, fieldId: string): boolean {
  return (
    state.activeTarget?.targetType === "form-field" &&
    state.activeTarget?.targetId === fieldId
  );
}

export function matchesTextFilter(
  query: string,
  parts: readonly (string | undefined | null)[],
  minChars = FILTER_MIN_CHARS,
): boolean {
  const q = query.trim();
  if (q.length < minChars) return true;
  const needle = q.toLowerCase();
  return parts.some((p) => p != null && String(p).toLowerCase().includes(needle));
}

export interface FieldDefinitionPatch {
  readonly label: string;
  readonly type: FieldType;
  readonly options?: readonly AttributeOption[];
  readonly defaultCurrency?: string;
}

export interface FormRendererProps {
  readonly schema: FormSchema;
  readonly values?: Readonly<Record<string, string>>;
  readonly onValueChange?: (fieldId: string, value: string) => void;
  readonly linkCounts?: Readonly<Record<string, number>>;
  readonly linkHints?: Readonly<Record<string, string>>;
  readonly showAddFieldForm?: boolean;
  readonly onRequestAddField?: () => void;
  readonly onConfirmAddField?: (patch: FieldDefinitionPatch) => void;
  readonly onCancelAddField?: () => void;
  readonly editingFieldId?: string | null;
  readonly onBeginEditField?: (fieldId: string) => void;
  readonly onSaveFieldEdit?: (fieldId: string, patch: FieldDefinitionPatch) => void;
  readonly onCancelFieldEdit?: () => void;
}

const iconButtonStyle: CSSProperties = {
  fontSize: 11,
  padding: "2px 6px",
  background: "white",
  border: "1px solid #888",
  borderRadius: 3,
  cursor: "pointer",
  lineHeight: 1,
};

const controlStyle: CSSProperties = {
  width: "100%",
  boxSizing: "border-box",
  fontSize: 13,
  padding: 4,
};

function ValueControl({
  field,
  value,
  onChange,
  onFocus,
}: {
  field: FormFieldSchema;
  value: string;
  onChange: (next: string) => void;
  onFocus: () => void;
}) {
  const type = normalizeAttributeType(field.type);
  const id = `field-${field.id}`;

  if (type === "text-long") {
    return (
      <textarea
        id={id}
        rows={2}
        value={value}
        onFocus={onFocus}
        onChange={(e) => onChange(e.target.value)}
        style={controlStyle}
      />
    );
  }

  if (type === "date" || type === "time") {
    return (
      <input
        id={id}
        type={type}
        value={value}
        onFocus={onFocus}
        onChange={(e) => onChange(e.target.value)}
        style={controlStyle}
      />
    );
  }

  if (type === "datetime") {
    // HTML datetime-local uses "YYYY-MM-DDTHH:MM" without timezone
    return (
      <input
        id={id}
        type="datetime-local"
        value={value}
        onFocus={onFocus}
        onChange={(e) => onChange(e.target.value)}
        style={controlStyle}
      />
    );
  }

  if (type === "amount") {
    const amount = parseAmountValue(value);
    const currency = amount.currency || field.defaultCurrency || DEFAULT_CURRENCY;
    return (
      <div style={{ display: "flex", gap: 6 }} data-testid={`field-${field.id}-amount`}>
        <input
          id={id}
          type="number"
          step="0.01"
          value={amount.value}
          onFocus={onFocus}
          onChange={(e) =>
            onChange(serializeAmountValue({ value: e.target.value, currency }))
          }
          style={{ ...controlStyle, flex: 1 }}
        />
        <input
          type="text"
          aria-label={`${field.label} currency`}
          value={currency}
          maxLength={3}
          onFocus={onFocus}
          onChange={(e) =>
            onChange(
              serializeAmountValue({
                value: amount.value,
                currency: e.target.value.toUpperCase(),
              }),
            )
          }
          data-testid={`field-${field.id}-currency`}
          style={{ ...controlStyle, width: 56, textTransform: "uppercase" }}
        />
      </div>
    );
  }

  if (type === "one-of") {
    const options = field.options ?? [];
    return (
      <select
        id={id}
        value={value}
        onFocus={onFocus}
        onChange={(e) => onChange(e.target.value)}
        style={controlStyle}
        data-testid={`field-${field.id}-select`}
      >
        <option value="">—</option>
        {options.map((opt) => (
          <option key={opt.id} value={opt.id}>
            {opt.label}
          </option>
        ))}
      </select>
    );
  }

  if (type === "some-of") {
    const selected = new Set(parseSomeOfValue(value));
    const options = field.options ?? [];
    return (
      <div
        role="group"
        aria-labelledby={`${id}-label`}
        data-testid={`field-${field.id}-checks`}
        style={{ display: "flex", flexDirection: "column", gap: 4 }}
      >
        {options.length === 0 && (
          <span style={{ fontSize: 11, color: "#888" }}>No options defined.</span>
        )}
        {options.map((opt) => (
          <label
            key={opt.id}
            style={{ display: "flex", gap: 6, alignItems: "center", fontSize: 12 }}
          >
            <input
              type="checkbox"
              checked={selected.has(opt.id)}
              onFocus={onFocus}
              onChange={(e) => {
                const next = new Set(selected);
                if (e.target.checked) next.add(opt.id);
                else next.delete(opt.id);
                onChange(serializeSomeOfValue([...next]));
              }}
            />
            {opt.label}
          </label>
        ))}
      </div>
    );
  }

  // text (default)
  return (
    <input
      id={id}
      type="text"
      value={value}
      onFocus={onFocus}
      onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
      style={controlStyle}
    />
  );
}

function FieldRow({
  field,
  value,
  linkCount,
  linkHint,
  isActive,
  isEditing,
  editLabel,
  editType,
  editOptionsText,
  editCurrency,
  onChange,
  onFocus,
  onBeginEdit,
  onChangeEditLabel,
  onChangeEditType,
  onChangeEditOptionsText,
  onChangeEditCurrency,
  onSaveEdit,
  onCancelEdit,
}: {
  field: FormFieldSchema;
  value: string;
  linkCount: number;
  linkHint?: string;
  isActive: boolean;
  isEditing: boolean;
  editLabel: string;
  editType: FieldType;
  editOptionsText: string;
  editCurrency: string;
  onChange: (next: string) => void;
  onFocus: () => void;
  onBeginEdit: () => void;
  onChangeEditLabel: (next: string) => void;
  onChangeEditType: (next: FieldType) => void;
  onChangeEditOptionsText: (next: string) => void;
  onChangeEditCurrency: (next: string) => void;
  onSaveEdit: () => void;
  onCancelEdit: () => void;
}) {
  const ref = useRef<HTMLDivElement>(null);
  useRegisterRect("field", field.id, ref);
  const typeLabel = displayTypeLabel(field.type);

  if (isEditing) {
    return (
      <div ref={ref} data-field-id={field.id} style={{ marginBottom: 12 }}>
        <FieldDefinitionForm
          label={editLabel}
          type={editType}
          optionsText={editOptionsText}
          defaultCurrency={editCurrency}
          onChangeLabel={onChangeEditLabel}
          onChangeType={onChangeEditType}
          onChangeOptionsText={onChangeEditOptionsText}
          onChangeDefaultCurrency={onChangeEditCurrency}
          onSave={onSaveEdit}
          onCancel={onCancelEdit}
          saveLabel="Save attribute"
          badge="Editing attribute"
          testidPrefix={`field-edit-${field.id}`}
        />
      </div>
    );
  }

  return (
    <div
      ref={ref}
      data-field-id={field.id}
      data-link-count={String(linkCount)}
      aria-current={isActive ? "true" : undefined}
      style={{
        position: "relative",
        marginBottom: 12,
        fontFamily: "system-ui, sans-serif",
        padding: 4,
        borderRadius: 4,
        background: isActive ? "#e8f0ff" : "transparent",
      }}
    >
      <button
        type="button"
        aria-label={`Edit ${field.id}`}
        data-testid={`field-edit-toggle-${field.id}`}
        title="Edit attribute key and type"
        onClick={(e) => {
          e.stopPropagation();
          onBeginEdit();
        }}
        style={{
          ...iconButtonStyle,
          position: "absolute",
          top: 4,
          right: 4,
          zIndex: 1,
        }}
      >
        ✎
      </button>
      <label
        id={`field-${field.id}-label`}
        htmlFor={
          normalizeAttributeType(field.type) === "some-of"
            ? undefined
            : `field-${field.id}`
        }
        style={{
          display: "block",
          fontSize: 12,
          fontWeight: 600,
          marginBottom: 4,
          paddingRight: 28,
        }}
      >
        {field.label}{" "}
        <span style={{ fontWeight: 400, color: "#666" }}>({typeLabel})</span>
        {linkCount > 0 ? (
          <span
            data-testid={`field-${field.id}-chip`}
            title={linkHint}
            style={{
              marginLeft: 8,
              padding: "1px 6px",
              borderRadius: 4,
              background: "#e7f0ff",
              color: "#0050b3",
              fontSize: 11,
              fontWeight: 500,
            }}
          >
            {linkCount} evidence
          </span>
        ) : null}
      </label>
      <ValueControl
        field={field}
        value={value}
        onChange={onChange}
        onFocus={onFocus}
      />
    </div>
  );
}

function buildPatch(
  label: string,
  type: FieldType,
  optionsText: string,
  currency: string,
): FieldDefinitionPatch {
  const t = normalizeAttributeType(type);
  const patch: FieldDefinitionPatch = { label, type: t };
  if (typeNeedsOptions(t)) {
    return { ...patch, options: parseOptionsText(optionsText) };
  }
  if (t === "amount") {
    return {
      ...patch,
      defaultCurrency: (currency || DEFAULT_CURRENCY).toUpperCase(),
    };
  }
  return patch;
}

export function FormRenderer({
  schema,
  values,
  onValueChange,
  linkCounts,
  linkHints,
  showAddFieldForm,
  onRequestAddField,
  onConfirmAddField,
  onCancelAddField,
  editingFieldId,
  onBeginEditField,
  onSaveFieldEdit,
  onCancelFieldEdit,
}: FormRendererProps) {
  const { state, focusTarget } = useActiveState();
  const [addLabel, setAddLabel] = useState("New attribute");
  const [addType, setAddType] = useState<FieldType>("text");
  const [addOptionsText, setAddOptionsText] = useState("");
  const [addCurrency, setAddCurrency] = useState(DEFAULT_CURRENCY);
  const [editLabel, setEditLabel] = useState("");
  const [editType, setEditType] = useState<FieldType>("text");
  const [editOptionsText, setEditOptionsText] = useState("");
  const [editCurrency, setEditCurrency] = useState(DEFAULT_CURRENCY);
  const [filterQuery, setFilterQuery] = useState("");

  const handleFocus = (fieldId: string) => {
    const target: EvidenceTarget = { targetType: "form-field", targetId: fieldId };
    focusTarget(target);
  };

  const beginEdit = (field: FormFieldSchema) => {
    setEditLabel(field.label);
    setEditType(normalizeAttributeType(field.type));
    setEditOptionsText(formatOptionsText(field.options));
    setEditCurrency(field.defaultCurrency ?? DEFAULT_CURRENCY);
    onBeginEditField?.(field.id);
  };

  const visibleFields = useMemo(
    () =>
      schema.fields.filter((field) => {
        const optionText = (field.options ?? [])
          .map((o) => `${o.id} ${o.label}`)
          .join(" ");
        return matchesTextFilter(filterQuery, [
          field.label,
          displayTypeLabel(field.type),
          field.id,
          values?.[field.id],
          optionText,
        ]);
      }),
    [schema.fields, filterQuery, values],
  );

  return (
    <div data-form-id={schema.id} style={{ padding: 12 }} role="group" aria-label="Attributes">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          marginBottom: 8,
          flexWrap: "wrap",
        }}
      >
        <h2
          style={{
            fontSize: 14,
            margin: 0,
            fontFamily: "system-ui, sans-serif",
            flex: "0 0 auto",
          }}
        >
          {schema.title}
        </h2>
        <input
          type="search"
          value={filterQuery}
          onChange={(e) => setFilterQuery(e.target.value)}
          placeholder="Filter…"
          aria-label="Filter attributes"
          data-testid="attributes-filter"
          style={{
            flex: "1 1 100px",
            minWidth: 80,
            fontSize: 12,
            padding: "4px 6px",
            border: "1px solid #ccc",
            borderRadius: 4,
          }}
        />
      </div>

      {visibleFields.length === 0 && schema.fields.length > 0 && (
        <p
          style={{ fontSize: 12, color: "#888", margin: "8px 0" }}
          data-testid="attributes-filter-empty"
        >
          No matches.
        </p>
      )}

      {visibleFields.map((field) => (
        <FieldRow
          key={field.id}
          field={field}
          value={values?.[field.id] ?? ""}
          linkCount={linkCounts?.[field.id] ?? 0}
          {...(linkHints?.[field.id] != null
            ? { linkHint: linkHints[field.id] }
            : {})}
          isActive={isFieldActive(state, field.id)}
          isEditing={editingFieldId === field.id}
          editLabel={editLabel}
          editType={editType}
          editOptionsText={editOptionsText}
          editCurrency={editCurrency}
          onChange={(next) => onValueChange?.(field.id, next)}
          onFocus={() => handleFocus(field.id)}
          onBeginEdit={() => beginEdit(field)}
          onChangeEditLabel={setEditLabel}
          onChangeEditType={setEditType}
          onChangeEditOptionsText={setEditOptionsText}
          onChangeEditCurrency={setEditCurrency}
          onSaveEdit={() =>
            onSaveFieldEdit?.(
              field.id,
              buildPatch(editLabel.trim(), editType, editOptionsText, editCurrency),
            )
          }
          onCancelEdit={() => onCancelFieldEdit?.()}
        />
      ))}

      {showAddFieldForm ? (
        <FieldDefinitionForm
          label={addLabel}
          type={addType}
          optionsText={addOptionsText}
          defaultCurrency={addCurrency}
          onChangeLabel={setAddLabel}
          onChangeType={setAddType}
          onChangeOptionsText={setAddOptionsText}
          onChangeDefaultCurrency={setAddCurrency}
          onSave={() =>
            onConfirmAddField?.(
              buildPatch(addLabel.trim(), addType, addOptionsText, addCurrency),
            )
          }
          onCancel={() => onCancelAddField?.()}
          saveLabel="Add attribute"
          badge="New attribute"
          testidPrefix="field-add"
        />
      ) : (
        <button
          type="button"
          data-testid="add-field-button"
          onClick={() => {
            setAddLabel(`New attribute ${schema.fields.length + 1}`);
            setAddType("text");
            setAddOptionsText("");
            setAddCurrency(DEFAULT_CURRENCY);
            onRequestAddField?.();
          }}
          style={{
            fontSize: 11,
            padding: "6px 10px",
            border: "1px dashed #888",
            borderRadius: 4,
            background: "white",
            cursor: "pointer",
            width: "100%",
            marginTop: 4,
          }}
        >
          Add attribute
        </button>
      )}
    </div>
  );
}
