import { useEffect, useMemo, useState } from "react";
import { Loader2 } from "lucide-react";
import {
  Modal,
  Button,
  TextInput,
  Textarea,
  MultiSelect,
  Select,
  Switch,
  Stack,
  Group,
  Text,
  Box,
  SimpleGrid,
  Divider,
} from "@mantine/core";

import {
  RULE_ENGINE_SELECT_NONE,
  type FormFieldDef,
} from "@/pages/ruleEngine/config/resources";
import type { RuleEngineRecord } from "@/types/rule-engine";
import { RULE_ENGINE_POSITION_END } from "./ruleEngineOrder.utils";

export interface RuleEngineFormDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  title: string;
  description: string;
  fields: FormFieldDef[];
  initialRecord?: RuleEngineRecord | null;
  isSubmitting: boolean;
  selectOptionsLoading?: boolean;
  positionOptions?: { label: string; value: string }[];
  positionLoading?: boolean;
  onSubmit: (values: Record<string, unknown>) => void;
}

type FormRow =
  | { kind: "pair"; fields: [FormFieldDef, FormFieldDef] }
  | { kind: "single"; field: FormFieldDef };

const isShortField = (field: FormFieldDef) =>
  field.type === "text" ||
  field.type === "number" ||
  field.type === "select" ||
  field.type === "date";

const buildFormRows = (fields: FormFieldDef[]): FormRow[] => {
  const rows: FormRow[] = [];
  let index = 0;

  while (index < fields.length) {
    const field = fields[index];

    if (field.type === "textarea" || field.type === "boolean") {
      rows.push({ kind: "single", field });
      index += 1;
      continue;
    }

    const next = fields[index + 1];
    if (next && isShortField(next)) {
      rows.push({ kind: "pair", fields: [field, next] });
      index += 2;
      continue;
    }

    rows.push({ kind: "single", field });
    index += 1;
  }

  return rows;
};

const buildInitialValues = (
  fields: FormFieldDef[],
  record?: RuleEngineRecord | null,
): Record<string, unknown> => {
  const values: Record<string, unknown> = {};
  for (const field of fields) {
    const raw = field.getInitialValue && record
      ? field.getInitialValue(record)
      : record?.[field.name];
    if (field.type === "multiselect") {
      values[field.name] = Array.isArray(raw) ? raw.map(String) : [];
    } else if (raw !== undefined && raw !== null) {
      if (field.type === "date" && typeof raw === "string") {
        values[field.name] = raw.slice(0, 10);
      } else if (Array.isArray(raw)) {
        values[field.name] = raw.join(", ");
      } else {
        values[field.name] = raw;
      }
    } else if (field.type === "boolean") {
      values[field.name] = false;
    } else if (field.type === "number") {
      values[field.name] = "";
    } else {
      values[field.name] = "";
    }
  }
  return values;
};

const resolveSelectValue = (
  field: FormFieldDef,
  values: Record<string, unknown>,
): string | undefined => {
  const raw = values[field.name];
  const isEmpty = raw === "" || raw === null || raw === undefined;

  if (field.optional && isEmpty) {
    return RULE_ENGINE_SELECT_NONE;
  }

  if (isEmpty) {
    return undefined;
  }

  return String(raw);
};

const inputStyles = {
  label: { fontWeight: 600, marginBottom: 6, color: "var(--mantine-color-gray-8)" },
} as const;

const FieldLabel = ({ label, required }: { label: string; required?: boolean }) => (
  <Group gap={4} wrap="nowrap">
    <span>{label}</span>
    {required ? (
      <Text component="span" c="red" size="sm">
        *
      </Text>
    ) : null}
  </Group>
);

const RuleEngineFormDialog = ({
  open,
  onOpenChange,
  title,
  fields,
  initialRecord,
  isSubmitting,
  selectOptionsLoading = false,
  positionOptions,
  positionLoading = false,
  onSubmit,
}: RuleEngineFormDialogProps) => {
  const [values, setValues] = useState<Record<string, unknown>>(() =>
    buildInitialValues(fields, initialRecord),
  );
  const [position, setPosition] = useState(RULE_ENGINE_POSITION_END);

  useEffect(() => {
    if (open) {
      setValues(buildInitialValues(fields, initialRecord));
      setPosition(RULE_ENGINE_POSITION_END);
    }
  }, [open, fields, initialRecord]);

  const visibleFields = useMemo(
    () =>
      fields.filter((field) => {
        if (
          field.hideWhen &&
          field.hideWhen.equals.includes(String(values[field.hideWhen.field] ?? ""))
        ) {
          return false;
        }
        if (
          field.showWhen &&
          !field.showWhen.equals.includes(String(values[field.showWhen.field] ?? ""))
        ) {
          return false;
        }
        if (field.showIf && !field.showIf(values)) return false;
        return true;
      }),
    [fields, values],
  );

  const formRows = useMemo(() => buildFormRows(visibleFields), [visibleFields]);

  const setField = (name: string, value: unknown) => {
    setValues((current) => {
      const next = { ...current, [name]: value };
      // Changing what a rate applies to (or its surcharge trigger) can invalidate
      // the previously-chosen unit — reset it so the admin re-picks from the new
      // allowed set instead of submitting a stale, rejected unit.
      if ((name === "appliesTo" || name === "trigger") && "rateUnit" in current) {
        next.rateUnit = "";
      }
      // The legal yards depend on what the rate is for and which way it runs, so
      // a leg picked under the old answer is no longer valid — clear it instead
      // of submitting a pair the API will reject.
      if (
        (name === "appliesTo" || name === "tradeDirection") &&
        "originYardId" in current
      ) {
        next.originYardId = "";
        next.destinationYardId = "";
      }
      // Intercity asks for a container type or a bulk cargo type, never both —
      // switching kind drops whichever the other kind had filled in.
      if (name === "intercityKind") {
        next.containerTypeId = "";
        next.cargoTypeId = "";
      }
      // Cargo kind (customs / lashing) decides both the container-type scope
      // and the legal units (container → per box/wagon, bulk → per ton/wagon).
      if (name === "cargoKind") {
        next.containerTypeId = "";
        next.cargoTypeId = "";
        next.rateUnit = "";
      }
      return next;
    });
  };

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    const payload: Record<string, unknown> = {};

    for (const field of visibleFields) {
      // Derived fields always submit their computed value — never stale state.
      const raw = field.computeValue
        ? (field.computeValue(values) ?? "")
        : values[field.name];
      if (field.type === "multiselect") {
        // Always the full replacement list — the API syncs the relation to it.
        payload[field.name] = Array.isArray(raw) ? raw : [];
      } else if (field.type === "number") {
        if (raw === "" || raw === undefined) continue;
        payload[field.name] = Number(raw);
      } else if (field.type === "boolean") {
        payload[field.name] = Boolean(raw);
      } else if (
        field.type === "select" &&
        (raw === "" || raw === RULE_ENGINE_SELECT_NONE)
      ) {
        if (!field.required) continue;
      } else if (raw === "" || raw === undefined) {
        if (!field.required) continue;
        payload[field.name] = raw;
      } else {
        payload[field.name] = raw;
      }
    }

    if (fields.some((f) => f.name === "code" && typeof payload.code === "string")) {
      payload.code = String(payload.code).toUpperCase();
    }

    if (!initialRecord && positionOptions && position !== RULE_ENGINE_POSITION_END) {
      payload.insertAfterId = position;
    }

    onSubmit(payload);
  };

  const renderField = (field: FormFieldDef) => {
    if (field.type === "boolean") {
      return (
        <Group
          key={field.name}
          justify="space-between"
          align="center"
          wrap="nowrap"
          gap="md"
          px="md"
          style={{
            minHeight: 42,
            background: "var(--mantine-color-gray-0)",
            border: "1px solid var(--mantine-color-gray-3)",
            borderRadius: "var(--mantine-radius-md)",
          }}
        >
          <Text size="sm" fw={600}>
            {field.label}
          </Text>
          <Switch
            checked={Boolean(values[field.name])}
            onChange={(e) => setField(field.name, e.currentTarget.checked)}
            size="md"
            color="edr-green"
          />
        </Group>
      );
    }

    const label = <FieldLabel label={field.label} required={field.required} />;

    if (field.type === "multiselect") {
      const options = field.optionsFromValues
        ? field.optionsFromValues(values)
        : (field.options ?? []);
      const selected = Array.isArray(values[field.name])
        ? (values[field.name] as string[])
        : [];
      return (
        <MultiSelect
          key={field.name}
          label={label}
          description={field.description}
          placeholder={
            selectOptionsLoading
              ? "Loading options..."
              : (field.placeholder ?? "Select one or more")
          }
          value={selected}
          onChange={(v) => setField(field.name, v)}
          disabled={selectOptionsLoading}
          data={options
            .filter((opt) => opt.value !== "" && opt.value !== RULE_ENGINE_SELECT_NONE)
            .map((opt) => ({ label: opt.label, value: opt.value }))}
          searchable
          clearable
          size="md"
          radius="md"
          styles={inputStyles}
        />
      );
    }

    if (field.type === "select") {
      // Dynamic options (e.g. rate unit) resolve from the live form values so
      // the choices track the other fields the admin has picked.
      const options = field.optionsFromValues ? field.optionsFromValues(values) : (field.options ?? []);
      return (
        <Select
          key={field.name}
          label={label}
          description={field.description}
          placeholder={
            selectOptionsLoading ? "Loading options..." : (field.placeholder ?? "Select an option")
          }
          value={resolveSelectValue(field, values)}
          onChange={(v) => setField(field.name, v === RULE_ENGINE_SELECT_NONE ? "" : v)}
          disabled={selectOptionsLoading}
          // Native required blocks submit while a mandatory select is empty —
          // without it the form posts and the API 400s (e.g. a container
          // customs/lashing rate with no container type picked).
          required={field.required}
          data={options
            .filter((opt) => opt.value !== "")
            .map((opt) => ({
              label: opt.label,
              value: opt.value,
            }))}
          searchable
          clearable
          size="md"
          radius="md"
          styles={inputStyles}
        />
      );
    }

    if (field.type === "textarea") {
      return (
        <Textarea
          key={field.name}
          label={label}
          value={String(values[field.name] ?? "")}
          onChange={(e) => setField(field.name, e.currentTarget.value)}
          placeholder={field.placeholder}
          required={field.required}
          minRows={4}
          autosize
          maxRows={8}
          size="md"
          radius="md"
          styles={inputStyles}
        />
      );
    }

    const isNumber = field.type === "number";
    const computed = field.computeValue ? field.computeValue(values) : undefined;

    return (
      <TextInput
        key={field.name}
        label={label}
        description={field.description}
        type={isNumber ? "number" : field.type === "date" ? "date" : "text"}
        // Every rule-engine number (sizes, capacities, counts, points, rates,
        // display order) is a non-negative magnitude — reject negatives outright
        // rather than letting a typed "-" reach the API.
        min={isNumber ? 0 : undefined}
        // Without an explicit step the browser assumes 1 and refuses to submit
        // anything fractional — which blocked decimal rates, tonnages and
        // distances. The API is the one that decides which of these are whole
        // numbers (@IsInt on points/sizes/order, @IsNumber on money, tons, km),
        // so let the field carry decimals and let a 400 catch the rest.
        step={isNumber ? "any" : undefined}
        disabled={field.disabled || computed !== undefined}
        value={String((computed !== undefined ? computed : values[field.name]) ?? "")}
        onChange={(e) => {
          const next = e.currentTarget.value;
          if (isNumber && next.trim().startsWith("-")) return;
          setField(field.name, next);
        }}
        placeholder={field.placeholder}
        required={field.required}
        size="md"
        radius="md"
        styles={inputStyles}
        rightSection={
          field.suffix ? (
            <Text size="sm" c="dimmed" fw={600} pr={4}>
              {field.suffix}
            </Text>
          ) : undefined
        }
        rightSectionWidth={field.suffix ? 52 : undefined}
      />
    );
  };

  return (
    <Modal
      opened={open}
      onClose={() => onOpenChange(false)}
      title={
        <Text size="lg" fw={700} lh={1.2}>
          {title}
        </Text>
      }
      centered
      size={720}
      radius="lg"
      padding="xl"
      overlayProps={{ backgroundOpacity: 0.45, blur: 3 }}
      styles={{
        content: {
          maxWidth: "min(720px, 95vw)",
        },
        body: {
          paddingTop: 20,
        },
      }}
    >
      <form onSubmit={handleSubmit}>
        <Stack gap="lg">
          <Box style={{ maxHeight: "calc(65vh - 120px)", overflowY: "auto", paddingRight: 4 }}>
            <Stack gap="md">
              {!initialRecord && positionOptions ? (
                <Select
                  label="Position"
                  description="New items are appended to the end by default."
                  value={position}
                  onChange={(value) => setPosition(value ?? RULE_ENGINE_POSITION_END)}
                  data={[
                    { label: "At end (default)", value: RULE_ENGINE_POSITION_END },
                    ...positionOptions,
                  ]}
                  searchable
                  disabled={positionLoading}
                  size="md"
                  radius="md"
                  styles={inputStyles}
                />
              ) : null}
              {formRows.map((row) =>
                row.kind === "pair" ? (
                  <SimpleGrid key={`${row.fields[0].name}-${row.fields[1].name}`} cols={2} spacing="md">
                    <Box style={{ minWidth: 0 }}>{renderField(row.fields[0])}</Box>
                    <Box style={{ minWidth: 0 }}>{renderField(row.fields[1])}</Box>
                  </SimpleGrid>
                ) : (
                  <Box key={row.field.name}>{renderField(row.field)}</Box>
                ),
              )}
            </Stack>
          </Box>

          <Divider />

          <Group justify="flex-end" gap="sm">
            <Button
              variant="default"
              onClick={() => onOpenChange(false)}
              disabled={isSubmitting}
              radius="md"
              size="md"
            >
              Cancel
            </Button>
            <Button
              type="submit"
              disabled={isSubmitting}
              leftSection={
                isSubmitting ? (
                  <Loader2 size={18} style={{ animation: "spin 1s linear infinite" }} />
                ) : undefined
              }
              radius="md"
              color="edr-green"
              variant="filled"
              fw={600}
              size="md"
            >
              {isSubmitting ? "Saving..." : "Save"}
            </Button>
          </Group>
        </Stack>
      </form>
    </Modal>
  );
};

export default RuleEngineFormDialog;
