import * as React from "react";
import { ChevronDownIcon, PlusIcon, Trash2Icon } from "lucide-react";
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import {
  Utils as QbUtils,
  type Actions,
  type ImmutableTree,
  type JsonItem,
} from "@react-awesome-query-builder/ui";
import { Button } from "@/components/ui/button";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import type { AlertQueryField, AlertQueryOperator } from "./types";
import {
  ALL_NUMERIC_OPERATORS,
  BOOLEAN_OPERATORS,
  OPERATOR_LABELS,
} from "./config";
import {
  allOperatorLabels,
  formatWithCommas,
  longestOf,
  parseCommas,
  ruleSummary,
} from "./utils";

// ---------------------------------------------------------------------------
// SelectAutoWidth
// ---------------------------------------------------------------------------

function SelectAutoWidth({
  longestLabel,
  children,
}: {
  longestLabel: string;
  children: React.ReactNode;
}) {
  return (
    <div className="relative inline-block shrink-0">
      <span
        aria-hidden
        className="invisible block h-8 whitespace-nowrap pl-3 pr-10 text-body-medium"
      >
        {longestLabel}
      </span>
      <div className="absolute inset-0">{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// ValueInput
// ---------------------------------------------------------------------------

function ValueInput({
  value,
  onChange,
  unit,
  min,
  max,
  placeholder = "0",
}: {
  value: string;
  onChange: (v: string) => void;
  unit?: "dollar" | "percent";
  min?: number;
  max?: number;
  placeholder?: string;
}) {
  function handleChange(raw: string) {
    const parsed = parseCommas(raw);
    if (parsed === "") {
      onChange("");
      return;
    }
    const n = parseFloat(parsed);
    if (Number.isNaN(n)) return;
    if (min !== undefined && n < min) return;
    if (max !== undefined && n > max) return;
    onChange(parsed);
  }

  return (
    <InputGroup className="w-36">
      {unit === "dollar" && (
        <InputGroupAddon align="inline-start">
          <InputGroupText>$</InputGroupText>
        </InputGroupAddon>
      )}
      <InputGroupInput
        type="text"
        inputMode="numeric"
        value={formatWithCommas(value)}
        onChange={(e) => handleChange(e.target.value)}
        placeholder={placeholder}
      />
      {unit === "percent" && (
        <InputGroupAddon align="inline-end">
          <InputGroupText>%</InputGroupText>
        </InputGroupAddon>
      )}
    </InputGroup>
  );
}

// ---------------------------------------------------------------------------
// RuleEditorFields
// ---------------------------------------------------------------------------

function RuleEditorFields({
  ruleProps,
  path,
  actions,
  fields,
  longestFieldLabel,
  longestOperatorLabel,
}: {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ruleProps: any;
  path: string[];
  actions: Actions;
  fields: AlertQueryField[];
  longestFieldLabel: string;
  longestOperatorLabel: string;
}) {
  const field: string = ruleProps?.field ?? fields[0]?.key ?? "";
  const operator: string = ruleProps?.operator ?? "greater_or_equal";
  const value0 = ruleProps?.value?.[0];

  const fieldDef = fields.find((f) => f.key === field) ?? fields[0];
  const isBooleanField = fieldDef?.type === "boolean";
  const availableOperators: AlertQueryOperator[] = isBooleanField
    ? BOOLEAN_OPERATORS
    : (fieldDef?.operators ?? ALL_NUMERIC_OPERATORS);

  function handleFieldChange(key: string) {
    actions.setField(path, key);
  }

  function handleOperatorChange(op: string) {
    actions.setOperator(path, op);
  }

  function handleValueChange(idx: number, raw: string) {
    const n = parseFloat(parseCommas(raw));
    if (!isNaN(n)) {
      actions.setValue(path, idx, n, "number");
    } else if (!raw) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      actions.setValue(path, idx, null as any, "number");
    }
  }

  function handleBoolChange(v: string) {
    actions.setValue(path, 0, v === "true", "boolean");
  }

  const valStr = (v: unknown): string => {
    if (v === null || v === undefined) return "";
    return String(v);
  };

  return (
    <div className="flex flex-wrap items-center gap-1.5">
      {/* Field */}
      <SelectAutoWidth longestLabel={longestFieldLabel}>
        <Select
          value={field}
          onValueChange={(v) => handleFieldChange(v as string)}
        >
          <SelectTrigger size="sm" className="w-full">
            <SelectValue>
              {fields.find((f) => f.key === field)?.label ?? field}
            </SelectValue>
          </SelectTrigger>
          <SelectContent>
            {fields.map((f) => (
              <SelectItem key={f.key} value={f.key}>
                {f.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </SelectAutoWidth>

      {/* Operator */}
      <SelectAutoWidth longestLabel={longestOperatorLabel}>
        <Select
          value={operator}
          onValueChange={(v) => handleOperatorChange(v as string)}
        >
          <SelectTrigger size="sm" className="w-full">
            <SelectValue>
              {OPERATOR_LABELS[operator as AlertQueryOperator] ?? operator}
            </SelectValue>
          </SelectTrigger>
          <SelectContent>
            {availableOperators.map((op) => (
              <SelectItem key={op} value={op}>
                {OPERATOR_LABELS[op]}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </SelectAutoWidth>

      {/* Value */}
      {isBooleanField ? (
        <Select
          value={String(value0 ?? true)}
          onValueChange={(v) => handleBoolChange(v as string)}
        >
          <SelectTrigger size="sm" className="w-36">
            <SelectValue>{(value0 ?? true) ? "Yes" : "No"}</SelectValue>
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="true">Yes</SelectItem>
            <SelectItem value="false">No</SelectItem>
          </SelectContent>
        </Select>
      ) : (
        <ValueInput
          value={valStr(value0)}
          onChange={(v) => handleValueChange(0, v)}
          unit={fieldDef?.unit}
          min={fieldDef?.min}
          max={fieldDef?.max}
        />
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// RuleAccordionItem
// ---------------------------------------------------------------------------

function RuleAccordionItem({
  ruleId,
  ruleProps,
  path,
  actions,
  fields,
  longestFieldLabel,
  longestOperatorLabel,
  removable,
}: {
  ruleId: string;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ruleProps: any;
  path: string[];
  actions: Actions;
  fields: AlertQueryField[];
  longestFieldLabel: string;
  longestOperatorLabel: string;
  removable: boolean;
}) {
  const summary = ruleSummary(ruleProps, fields);

  return (
    <AccordionPrimitive.Item value={ruleId} className="border-b">
      <AccordionPrimitive.Header className="flex items-center">
        <AccordionPrimitive.Trigger
          className={cn(
            "flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
            "rounded-none hover:underline",
            "focus-visible:ring-2 focus-visible:ring-foreground/30",
            "[&[data-panel-open]>svg:last-child]:rotate-180",
          )}
        >
          <span className="flex-1 text-sm text-foreground">{summary}</span>
          <ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
        </AccordionPrimitive.Trigger>
        <Button
          type="button"
          variant="ghost"
          size="sm"
          className="shrink-0 px-2"
          onClick={() => actions.removeRule(path)}
          disabled={!removable}
          aria-label="Remove rule"
        >
          <Trash2Icon className="size-4" />
        </Button>
      </AccordionPrimitive.Header>
      <AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
        <div className="pb-3">
          <RuleEditorFields
            ruleProps={ruleProps}
            path={path}
            actions={actions}
            fields={fields}
            longestFieldLabel={longestFieldLabel}
            longestOperatorLabel={longestOperatorLabel}
          />
        </div>
      </AccordionPrimitive.Panel>
    </AccordionPrimitive.Item>
  );
}

// ---------------------------------------------------------------------------
// SubGroupAccordionItem
// ---------------------------------------------------------------------------

function SubGroupAccordionItem({
  groupId,
  group,
  path,
  actions,
  fields,
  longestFieldLabel,
  longestOperatorLabel,
}: {
  groupId: string;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  group: any;
  path: string[];
  actions: Actions;
  fields: AlertQueryField[];
  longestFieldLabel: string;
  longestOperatorLabel: string;
}) {
  const conjunction: string = group.properties?.conjunction ?? "AND";
  const nestedChildren: JsonItem[] = group.children1 ?? [];
  const count = nestedChildren.length;
  const triggerLabel = `Group (${count} ${count === 1 ? "rule" : "rules"})`;

  return (
    <AccordionPrimitive.Item value={groupId} className="border-b">
      <AccordionPrimitive.Header className="flex items-center">
        <AccordionPrimitive.Trigger
          className={cn(
            "flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
            "rounded-none hover:underline",
            "focus-visible:ring-2 focus-visible:ring-foreground/30",
            "[&[data-panel-open]>svg:last-child]:rotate-180",
          )}
        >
          <span className="flex-1 text-sm font-medium text-foreground">
            {triggerLabel}
          </span>
          <ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
        </AccordionPrimitive.Trigger>
        <Button
          type="button"
          variant="ghost"
          size="sm"
          className="shrink-0 px-2"
          onClick={() => actions.removeGroup(path)}
          aria-label="Remove group"
        >
          <Trash2Icon className="size-4" />
        </Button>
      </AccordionPrimitive.Header>
      <AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
        <div className="pb-3 pl-4 border-l ml-2 flex flex-col gap-3">
          {/* Nested combinator */}
          <div className="flex items-center gap-2">
            <Checkbox
              id={`subgroup-${groupId}-combinator`}
              checked={conjunction === "AND"}
              onCheckedChange={(checked) =>
                actions.setConjunction(path, checked ? "AND" : "OR")
              }
            />
            <Label
              htmlFor={`subgroup-${groupId}-combinator`}
              className="cursor-pointer font-normal text-sm"
            >
              Match all conditions
            </Label>
            <span className="text-xs text-muted-foreground">
              ({conjunction})
            </span>
          </div>

          {/* Nested rules — flat rows */}
          {nestedChildren.map((child) => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const c = child as any;
            const ruleId: string = c.id ?? "";
            const rulePath = [...path, ruleId];
            return (
              <div key={ruleId} className="flex items-start gap-1.5">
                <div className="flex-1">
                  <RuleEditorFields
                    ruleProps={c.properties}
                    path={rulePath}
                    actions={actions}
                    fields={fields}
                    longestFieldLabel={longestFieldLabel}
                    longestOperatorLabel={longestOperatorLabel}
                  />
                </div>
                <Button
                  type="button"
                  variant="ghost"
                  size="sm"
                  className="shrink-0 px-2"
                  onClick={() => actions.removeRule(rulePath)}
                  disabled={nestedChildren.length <= 1}
                  aria-label="Remove rule"
                >
                  <Trash2Icon className="size-4" />
                </Button>
              </div>
            );
          })}

          {/* Add rule inside group */}
          <Button
            type="button"
            variant="ghost"
            size="sm"
            onClick={() => actions.addRule(path)}
            className="w-fit"
          >
            <PlusIcon className="size-4" />
            Add rule
          </Button>
        </div>
      </AccordionPrimitive.Panel>
    </AccordionPrimitive.Item>
  );
}

// ---------------------------------------------------------------------------
// CustomBuilderUI
// ---------------------------------------------------------------------------

export interface CustomBuilderUIProps {
  tree: ImmutableTree;
  actions: Actions;
  fields: AlertQueryField[];
  defaultOpenItems: string[];
  className?: string;
}

export function CustomBuilderUI({
  tree,
  actions,
  fields,
  defaultOpenItems,
  className,
}: CustomBuilderUIProps) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const treeJson = QbUtils.getTree(tree) as any;
  const rootId: string = treeJson?.id ?? "root";
  const conjunction: string = treeJson?.properties?.conjunction ?? "AND";
  const children: JsonItem[] = treeJson?.children1 ?? [];

  const longestFieldLabel = React.useMemo(
    () => longestOf(fields.map((f) => f.label)),
    [fields],
  );
  const longestOperatorLabel = React.useMemo(
    () => longestOf(allOperatorLabels(fields)),
    [fields],
  );

  return (
    <div className={cn("flex flex-col gap-3", className)}>
      {/* Root combinator */}
      <div className="flex items-center gap-2">
        <Checkbox
          id="qb-root-combinator"
          checked={conjunction === "AND"}
          onCheckedChange={(checked) =>
            actions.setConjunction([rootId], checked ? "AND" : "OR")
          }
        />
        <Label
          htmlFor="qb-root-combinator"
          className="cursor-pointer font-normal text-sm"
        >
          Match all conditions
        </Label>
        <span className="text-xs text-muted-foreground">({conjunction})</span>
      </div>

      {/* Rules and groups */}
      <AccordionPrimitive.Root
        multiple
        defaultValue={defaultOpenItems}
        className="border-t"
      >
        {children.map((child) => {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const c = child as any;
          const childId: string = c.id ?? "";
          const childPath = [rootId, childId];

          if (c.type === "group") {
            return (
              <SubGroupAccordionItem
                key={childId}
                groupId={childId}
                group={c}
                path={childPath}
                actions={actions}
                fields={fields}
                longestFieldLabel={longestFieldLabel}
                longestOperatorLabel={longestOperatorLabel}
              />
            );
          }

          return (
            <RuleAccordionItem
              key={childId}
              ruleId={childId}
              ruleProps={c.properties}
              path={childPath}
              actions={actions}
              fields={fields}
              longestFieldLabel={longestFieldLabel}
              longestOperatorLabel={longestOperatorLabel}
              removable={children.length > 1}
            />
          );
        })}
      </AccordionPrimitive.Root>

      {/* Action buttons */}
      <div className="flex gap-2">
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() => actions.addRule([rootId])}
        >
          <PlusIcon className="size-4" />
          Add rule
        </Button>
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() => actions.addGroup([rootId])}
        >
          <PlusIcon className="size-4" />
          Add group
        </Button>
      </div>
    </div>
  );
}
