import { ScrollArea } from "@base-ui/react/scroll-area";
import { Close, Search } from "@mui/icons-material";
import { Button, Chip, Paper, Stack, SxProps, TextField } from "@mui/material";
import { useField, useStore } from "@tanstack/react-form";
import dayjs from "dayjs";
import { useEffect, useMemo, useRef } from "react";
import { v4 as uuidv4 } from "uuid";

import { DynamicEnumField, Field } from "./fields";
import { useAppForm, withForm } from "./hooks/form";
import { IncomingFilter } from "./mapper";
import { canBeCombinedWith } from "./rules";
import { sharedFormOptions, validators } from "./shared";
import { EnumOperators, Operator, Operators } from "./types";
import { getFieldLabel, getOperatorLabel } from "./utils";

const ROW_SCROLL_OFFSET = 100;

type InferDynamicEnumArg<Field extends DynamicEnumField<unknown>> = Field extends DynamicEnumField<infer Arg> ? Arg : never;

type ExtractFieldsByType<Fields extends readonly Field[], Type extends Field["type"]> = Extract<Fields[number], { type: Type }>;

type DynamicEnumArgMap<
  Fields extends readonly Field[],
  Dynamic extends ExtractFieldsByType<Fields, "dynamic_enum"> = ExtractFieldsByType<Fields, "dynamic_enum">,
> = {
  [K in Dynamic["name"]]: InferDynamicEnumArg<Dynamic>;
};

type FilterRowProps<TFields extends readonly Field[] = readonly Field[]> = {
  filters: IncomingFilter[];
  onFiltersChange: (filters: IncomingFilter[]) => void;
  fieldConfig: TFields | readonly Field[];
  dynamicEnumArgMap: DynamicEnumArgMap<TFields>;
  loading?: boolean;
  sx?: SxProps;
  getNameLabel?: (name: string) => string;
  onTrack?: (event: string, properties?: Record<string, unknown>) => void;
};

export function FilterRow<TFields extends readonly Field[]>({
  filters,
  onFiltersChange,
  fieldConfig: fields,
  dynamicEnumArgMap,
  loading = false,
  sx = [],
  getNameLabel = getFieldLabel,
  onTrack,
}: FilterRowProps<TFields>) {
  // Type assertion to handle readonly arrays
  const fieldsArray = fields as readonly Field[];
  const scrollableRef = useRef<HTMLDivElement>(null);
  const hasInitializedRef = useRef(false);
  const previousFiltersLengthRef = useRef(filters.length);

  const isFilterComplete = (item: { name: string; operator: Operator | ""; value: string | string[] | null }): boolean => {
    // Check if name is non-empty
    if (!item.name || item.name.trim() === "") {
      return false;
    }

    // Check if operator is non-empty and valid (not just "")
    if (!item.operator) {
      return false;
    }

    // Check if value is non-empty (string or array)
    if (Array.isArray(item.value)) {
      return item.value.length > 0;
    }
    return typeof item.value === "string" && item.value.trim() !== "" && item.value !== null;
  };

  const form = useAppForm({
    ...sharedFormOptions,
    onSubmit: async ({ value }) => {
      // Filter out incomplete filters before mapping and setting them
      const completeFilters = value.config.filter(isFilterComplete);
      const hadFilters = previousFiltersLengthRef.current > 0;
      const nextFilters = completeFilters.map(({ id: _, ...item }) => item) as IncomingFilter[];

      if (nextFilters.length > 0) {
        onTrack?.("TRACING_FILTERS_APPLIED", {
          filter_count: nextFilters.length,
          filter_fields: nextFilters.map((filter) => filter.name),
          filter_operators: nextFilters.map((filter) => filter.operator),
          source: "filters_row",
        });
      } else if (hadFilters) {
        onTrack?.("TRACING_FILTERS_CLEARED", {
          previous_filter_count: previousFiltersLengthRef.current,
          source: "filters_row",
        });
      }

      previousFiltersLengthRef.current = nextFilters.length;
      onFiltersChange(nextFilters);
    },
  });

  // Get current form config to check if form is empty
  const currentFormConfig = useStore(form.store, (state) => state.values.config);

  // Sync form with filters prop when they're loaded from URL
  // This only runs once when filters are first loaded
  useEffect(() => {
    // Only initialize if:
    // 1. We haven't initialized yet
    // 2. Filters prop has filters
    // 3. Form is currently empty (to avoid overwriting user edits)
    if (!hasInitializedRef.current && filters.length > 0 && currentFormConfig.length === 0) {
      const configFromFilters = filters.map((filter) => ({
        id: uuidv4(),
        name: filter.name,
        operator: filter.operator,
        value: filter.value,
      }));
      form.setFieldValue("config", configFromFilters);
      hasInitializedRef.current = true;
      previousFiltersLengthRef.current = filters.length;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filters, currentFormConfig.length]); // Watch filters prop and form state

  const handleClose = () => {
    if (!scrollableRef.current) return;
    const offsetToEnd = scrollableRef.current.scrollWidth - scrollableRef.current.clientWidth - ROW_SCROLL_OFFSET;

    scrollableRef.current.scrollTo({ left: offsetToEnd, behavior: "smooth" });
  };

  return (
    <Paper variant="outlined" sx={[{ p: 2 }, ...(Array.isArray(sx) ? sx : [sx])]}>
      <ScrollArea.Root
        render={
          <form
            onSubmit={(e) => {
              e.preventDefault();
              e.stopPropagation();
              form.handleSubmit();
            }}
          />
        }
        className="grid grid-cols-[1fr_max-content] gap-2"
      >
        <ScrollArea.Viewport
          ref={scrollableRef}
          className="p-2 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded h-full has-[input:hover]:bg-gray-100 dark:has-[input:hover]:bg-gray-700 transition-colors duration-100"
        >
          <ScrollArea.Content className="flex flex-row gap-2 h-full text-xs">
            <form.Field mode="array" name="config">
              {(field) =>
                field.state.value.map((item, index) => (
                  <FilterItem
                    key={item.id}
                    index={index}
                    onRemove={() => field.removeValue(index)}
                    onClose={handleClose}
                    form={form}
                    getNameLabel={getNameLabel}
                    fields={fieldsArray}
                    dynamicEnumArgMap={dynamicEnumArgMap}
                  />
                ))
              }
            </form.Field>

            <form.Field mode="array" name="config">
              {(field) => (
                <input
                  placeholder={!field.state.value.length ? `Add filters to narrow down the results...` : undefined}
                  className="min-w-[200px] flex-1 outline-none bg-transparent placeholder:text-gray-600 dark:placeholder:text-gray-400 dark:text-gray-100 text-xs h-full"
                  onFocus={() => field.pushValue({ name: "", operator: "", value: "", id: uuidv4() })}
                />
              )}
            </form.Field>
          </ScrollArea.Content>
        </ScrollArea.Viewport>
        <form.Subscribe selector={(state) => state.canSubmit}>
          {(canSubmit) => (
            <Button
              type="submit"
              disabled={!canSubmit}
              variant="outlined"
              loading={loading}
              sx={{
                alignSelf: "center",
              }}
              startIcon={<Search />}
            >
              Filter
            </Button>
          )}
        </form.Subscribe>
      </ScrollArea.Root>
    </Paper>
  );
}

const FilterItem = withForm({
  ...sharedFormOptions,
  props: {} as {
    index: number;
    onRemove: () => void;
    onClose: () => void;
    getNameLabel: (name: string) => string;
    fields: readonly Field[];
    dynamicEnumArgMap: DynamicEnumArgMap<readonly Field[]>;
  },
  render: function Render({ form, index, onRemove, onClose, getNameLabel, fields, dynamicEnumArgMap }) {
    const allMetrics = useStore(form.store, (state) => state.values.config.slice(0, index));
    const field = useField({ form, name: `config[${index}]` as const });

    const config = useStore(field.store, (state) => state.value);

    const operatorItems = useMemo(
      () => getAvailableOperators(allMetrics, config?.name ?? "", fields)?.map((operator) => operator),
      [allMetrics, config?.name, fields]
    );

    const stage = (() => {
      if (!config) return 0;
      switch (true) {
        case !!config.name && !!config.operator && config.value !== "":
          return 3;
        case !!config.name && !!config.operator:
          return 2;
        case !!config.name:
          return 1;
        default:
          return 0;
      }
    })();

    return (
      <Stack direction="row" className="group shrink-0" data-stage={stage}>
        <form.AppField
          name={`config[${index}].name` as const}
          validators={{
            onMount: validators.name,
            onChange: validators.name,
          }}
        >
          {(field) => (
            <field.MaterialAutocompleteField
              disablePortal
              options={fields.map(({ name }) => name)}
              getOptionLabel={getNameLabel}
              size="small"
              onClose={() => {
                field.handleBlur();
                onClose();
              }}
              sx={{
                width: 200,
                "& .MuiAutocomplete-inputRoot": {
                  borderTopRightRadius: 0,
                  borderBottomRightRadius: 0,
                  "& fieldset": {
                    borderRightWidth: 0,
                  },
                },
              }}
              renderInput={(params) => <TextField {...params} variant="filled" label="Field" />}
            />
          )}
        </form.AppField>

        {stage >= 1 && (
          <form.AppField
            name={`config[${index}].operator` as const}
            listeners={{
              onChange: ({ value }) => {
                const isMultiple = value === EnumOperators.IN;

                form.resetField(`config[${index}].value`);
                form.setFieldValue(`config[${index}].value`, isMultiple ? [] : null);
              },
            }}
            validators={{
              onMount: validators.operator,
              onChange: validators.operator,
            }}
          >
            {(field) => (
              <field.MaterialAutocompleteField
                disablePortal
                options={operatorItems}
                multiple={false}
                getOptionLabel={getOperatorLabel}
                onClose={() => {
                  field.handleBlur();
                  onClose();
                }}
                size="small"
                sx={{
                  width: 150,
                  "& .MuiAutocomplete-inputRoot": {
                    borderRadius: 0,
                    "& fieldset": {
                      borderInlineWidth: 0,
                    },
                  },
                }}
                renderInput={(params) => <TextField {...params} variant="filled" label="Operator" />}
              />
            )}
          </form.AppField>
        )}
        {stage >= 2 && <ValueInput form={form} index={index} onClose={onClose} fields={fields} dynamicEnumArgMap={dynamicEnumArgMap} />}
        <Button size="small" variant="outlined" color="error" disableElevation onClick={onRemove} className="rounded-l-none!">
          <Close sx={{ fontSize: 16 }} />
        </Button>
      </Stack>
    );
  },
});

const ValueInput = withForm({
  ...sharedFormOptions,
  props: {} as {
    index: number;
    onClose: () => void;
    fields: readonly Field[];
    dynamicEnumArgMap: DynamicEnumArgMap<readonly Field[]>;
  },
  render: function Render({ form, index, onClose, fields, dynamicEnumArgMap }) {
    const field = useField({ form, name: `config[${index}]` as const });

    const config = useStore(field.store, (state) => state.value);

    // Guard against undefined config during array mutations (same pattern as FilterItem)
    if (!config) return null;

    const { name } = config;

    const fieldConfig = fields.find((field) => field.name === name);

    if (!fieldConfig) return null;

    let fieldValidators = {};
    if (fieldConfig.type === "enum" || fieldConfig.type === "dynamic_enum") {
      const multiple = config.operator === EnumOperators.IN;
      fieldValidators = {
        onMount: multiple ? validators.valueArray : validators.value,
        onChange: multiple ? validators.valueArray : validators.value,
      };
    } else if (fieldConfig.type === "boolean") {
      fieldValidators = {
        onMount: validators.value,
        onChange: validators.value,
      };
    } else if (fieldConfig.type === "numeric") {
      fieldValidators = {
        onMount: validators.numeric(fieldConfig.min ?? -Infinity, fieldConfig.max ?? Infinity),
        onChange: validators.numeric(fieldConfig.min ?? -Infinity, fieldConfig.max ?? Infinity),
      };
    } else if (fieldConfig.type === "text") {
      const multiple = config.operator === Operators.IN;
      fieldValidators = {
        onMount: multiple ? validators.valueArray : validators.value,
        onChange: multiple ? validators.valueArray : validators.value,
      };
    } else if (fieldConfig.type === "date") {
      fieldValidators = {
        onMount: validators.date,
        onChange: validators.date,
      };
    }

    return (
      <form.AppField key={index} name={`config[${index}].value` as const} validators={fieldValidators}>
        {(field) => {
          if (fieldConfig.type === "boolean") {
            return (
              <field.MaterialAutocompleteField
                disablePortal
                options={["true", "false"]}
                getOptionLabel={(option) => (option === "true" ? "True" : "False")}
                multiple={false}
                autoHighlight={false}
                onClose={onClose}
                size="small"
                sx={{
                  width: 200,
                  minWidth: 200,
                  "& .MuiAutocomplete-inputRoot": {
                    borderRadius: 0,
                    "& fieldset": {
                      borderInlineWidth: 0,
                    },
                  },
                }}
                renderInput={(params) => <TextField {...params} variant="filled" label="Value" />}
              />
            );
          }

          if (fieldConfig.type === "enum") {
            const multiple = config.operator === EnumOperators.IN;
            return (
              <field.MaterialAutocompleteField
                disablePortal
                options={fieldConfig.options}
                getOptionLabel={(option) => fieldConfig.itemToStringLabel?.(option) ?? option}
                multiple={multiple}
                autoHighlight={false}
                onClose={onClose}
                size="small"
                sx={{
                  width: multiple ? "max-content" : 200,
                  minWidth: 200,
                  "& .MuiAutocomplete-inputRoot": {
                    borderRadius: 0,
                    "& fieldset": {
                      borderInlineWidth: 0,
                    },
                  },
                }}
                limitTags={1}
                renderInput={(params) => <TextField {...params} variant="filled" label="Value" />}
              />
            );
          }

          if (fieldConfig.type === "dynamic_enum") {
            return (
              <DynamicEnumInput
                form={form}
                config={fieldConfig as Extract<Field, { type: "dynamic_enum" }>}
                index={index}
                onClose={onClose}
                dynamicEnumArgMap={dynamicEnumArgMap}
              />
            );
          }

          if (fieldConfig.type === "numeric") {
            return (
              <field.NumberField
                onValueChange={(value) => {
                  field.handleChange(value?.toString() ?? "");
                }}
                onBlur={() => {
                  field.handleBlur();
                  onClose();
                }}
                value={field.state.value ? Number(field.state.value) : null}
                min={fieldConfig.min}
                max={fieldConfig.max}
                className="overflow-hidden"
              />
            );
          }

          if (fieldConfig.type === "text") {
            return <TextInput form={form} index={index} onClose={onClose} />;
          }

          if (fieldConfig.type === "date") {
            return <DatePickerInput form={form} index={index} onClose={onClose} />;
          }
        }}
      </form.AppField>
    );
  },
});

const TextInput = withForm({
  ...sharedFormOptions,
  props: {} as {
    index: number;
    onClose: () => void;
  },
  render: function Render({ form, index, onClose }) {
    const field = useField({ form, name: `config[${index}]` as const });
    const operator = useStore(field.store, (state) => state.value.operator);

    const multiple = operator === Operators.IN;

    if (multiple) {
      return (
        <form.AppField
          name={`config[${index}].value` as const}
          validators={{
            onMount: validators.valueArray,
            onChange: validators.valueArray,
          }}
        >
          {(field) => (
            <field.MaterialAutocompleteField
              freeSolo
              multiple
              onClose={onClose}
              onBlur={() => {
                field.handleBlur();
                onClose();
              }}
              onChange={(_, value) => {
                field.handleChange(value);
              }}
              value={field.state.value as string[]}
              options={[]}
              size="small"
              limitTags={1}
              renderValue={(value: readonly string[], getItemProps) =>
                value.map((option: string, index: number) => {
                  const { key, ...itemProps } = getItemProps({ index });
                  return <Chip variant="outlined" size="small" label={option} key={key} sx={{ fontSize: 12 }} {...itemProps} />;
                })
              }
              renderInput={(params) => (
                <TextField
                  {...params}
                  variant="filled"
                  label="Value"
                  sx={{
                    minWidth: 200,
                    "& .MuiAutocomplete-inputRoot": {
                      borderRadius: 0,
                      "& fieldset": {
                        borderInlineWidth: 0,
                      },
                    },
                  }}
                />
              )}
            />
          )}
        </form.AppField>
      );
    }

    return (
      <form.AppField
        name={`config[${index}].value` as const}
        validators={{
          onMount: validators.value,
          onChange: validators.value,
        }}
      >
        {(field) => (
          <TextField
            value={field.state.value as string}
            onChange={(e) => field.handleChange(e.target.value)}
            onBlur={() => {
              field.handleBlur();
            }}
            sx={{
              width: 200,
              "& .MuiAutocomplete-inputRoot": {
                borderRadius: 0,
                "& fieldset": {
                  borderInlineWidth: 0,
                },
              },
            }}
            variant="filled"
            label="Value"
            size="small"
          />
        )}
      </form.AppField>
    );
  },
});

const DynamicEnumInput = withForm({
  ...sharedFormOptions,
  props: {} as {
    config: Extract<Field, { type: "dynamic_enum" }>;
    index: number;
    onClose: () => void;
    dynamicEnumArgMap: DynamicEnumArgMap<readonly Field[]>;
  },
  render: function Render({ config, form, index, onClose, dynamicEnumArgMap }) {
    const ctx = dynamicEnumArgMap[config.name as keyof typeof dynamicEnumArgMap];
    const { data, loading } = config.useData(ctx);

    const field = useField({ form, name: `config[${index}]` as const });

    const operator = useStore(field.store, (state) => state.value.operator);

    const multiple = operator === EnumOperators.IN;

    return (
      <form.AppField name={`config[${index}].value` as const}>
        {(field) => (
          <field.MaterialAutocompleteField
            disablePortal
            onClose={onClose}
            options={data}
            multiple={multiple}
            limitTags={1}
            loading={loading}
            size="small"
            sx={{
              width: multiple ? "max-content" : undefined,
              minWidth: 200,
              "& .MuiAutocomplete-input": {
                width: multiple ? undefined : "max-content !important",
              },
              "& .MuiAutocomplete-inputRoot": {
                borderRadius: 0,
                "& fieldset": {
                  borderInlineWidth: 0,
                },
              },
            }}
            renderInput={(params) => <TextField {...params} variant="filled" label="Value" />}
          />
        )}
      </form.AppField>
    );
  },
});

const DatePickerInput = withForm({
  ...sharedFormOptions,
  props: {} as {
    index: number;
    onClose: () => void;
  },
  render: function Render({ form, index, onClose }) {
    return (
      <form.AppField
        name={`config[${index}].value` as const}
        validators={{
          onMount: validators.date,
          onChange: validators.date,
        }}
      >
        {(field) => (
          <field.DatePickerField
            value={field.state.value ? dayjs(field.state.value as string) : null}
            onClose={onClose}
            sx={{
              width: 200,
              "& .MuiInputBase-root": {
                borderRadius: 0,
              },
              "& .MuiOutlinedInput-notchedOutline": {
                borderInlineWidth: 0,
              },
            }}
            slotProps={{
              textField: {
                variant: "filled",
                label: "Value",
                size: "small",
              },
            }}
          />
        )}
      </form.AppField>
    );
  },
});

function getAvailableOperators(metrics: { name: string; operator: string }[], key: string, fields: readonly Field[]): Operator[] {
  if (!key || key === "") return [];

  const field = fields.find((field) => field.name === key);
  if (!field) return [];

  const used = metrics.filter((m) => m.name === key && m.operator !== "");
  if (!used.length) return field.operators;

  return field.operators
    .filter((operator) => !used.some((m) => m.operator === operator))
    .filter((operator) => used.every((m) => m.operator && canBeCombinedWith(operator, m.operator as Operator)));
}
