"use client";

import { useMemo, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Building2, FileUp, Map, MapPin, Pencil, Plus, Trash2, X } from "lucide-react";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { DebouncedSearch } from "@/components/shared/debounced-search";
import { Pagination } from "@/components/shared/pagination";
import { EmptyState, ErrorState, TableSkeleton } from "@/components/shared/states";
import { StatusBadge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { BulkEditCitiesDialog } from "@/features/cities/bulk-edit-cities-dialog";
import { CityDialog } from "@/features/cities/city-dialog";
import { ImportCitiesDialog } from "@/features/cities/import-cities-dialog";
import { useCan } from "@/features/auth/use-can";
import { useI18n, useT } from "@/i18n";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatCurrency, formatNumber } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { deleteCity, flattenCities, getCities } from "@/services/cities";
import type { City } from "@/types/city";

/** Areas shown a page at a time: hundreds of them make one table nobody can find a district in. */
const AREAS_PER_PAGE = 25;

/** A name as it is searched: Arabic spelled either way (إدلب / ادلب, ة / ه, ى / ي) and without harakat matches. */
function fold(name: string): string {
  return name
    .toLowerCase()
    .replace(/[\u064B-\u0652\u0640]/g, "")
    .replace(/[أإآ]/g, "ا")
    .replace(/ة/g, "ه")
    .replace(/ى/g, "ي")
    .trim();
}

/**
 * Where the company delivers, and what a delivery there earns.
 *
 * **Districts are drawn under their city, indented, and the rate says where it came from.** The
 * inheritance is the point of the screen: a district marked as following its city changes when the
 * city changes, and one with its own rate does not. A table that showed only figures would make those
 * two look identical and the next rate change a surprise.
 */
export function CityTree({ currency }: { currency: string }) {
  const t = useT();
  const { locale } = useI18n();
  const { can } = useCan();
  const canManage = can("cities.manage");

  const queryClient = useQueryClient();

  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: queryKeys.cities.tree,
    queryFn: async () => (await getCities()).Model ?? [],
  });

  const cities = useMemo(() => data ?? [], [data]);
  const rows = useMemo(() => flattenCities(cities), [cities]);

  /*
   * Narrowed and paged here, not on the server: the tree comes whole (it is also the order form's select), and a city's
   * districts are found by name or by picking the city. Districts stay under their city, in the tree's own order.
   */
  const [search, setSearch] = useState("");
  const [cityFilter, setCityFilter] = useState("all");
  const [page, setPage] = useState(1);

  const shown = useMemo(() => {
    const term = fold(search);
    return rows.filter(
      ({ city, parent }) =>
        (cityFilter === "all" || city.uuid === cityFilter || parent?.uuid === cityFilter) &&
        (term === "" || fold(city.name).includes(term)),
    );
  }, [rows, search, cityFilter]);

  const totalPages = Math.max(1, Math.ceil(shown.length / AREAS_PER_PAGE));
  const currentPage = Math.min(page, totalPages);
  const pageRows = shown.slice((currentPage - 1) * AREAS_PER_PAGE, currentPage * AREAS_PER_PAGE);
  const onPage = new Set(pageRows.map((row) => row.city.uuid));

  const [dialog, setDialog] = useState<
    | { kind: "city" }
    | { kind: "district"; parent: City }
    | { kind: "edit"; city: City; parent: City | null }
    | null
  >(null);
  const [removing, setRemoving] = useState<City | null>(null);
  const [importing, setImporting] = useState(false);

  /*
   * The areas ticked for a bulk edit, by uuid. Kept as uuids rather than rows so a reload of the tree (after any save) keeps the
   * selection pointing at the same areas, and an area that disappeared simply drops out of it.
   */
  const [selected, setSelected] = useState<Set<string>>(() => new Set());
  const [bulkEditing, setBulkEditing] = useState(false);
  const selectedAreas = rows.map((row) => row.city).filter((city) => selected.has(city.uuid));
  // The header box answers for the page in view; what is ticked on other pages stays ticked.
  const pageSelected = pageRows.filter((row) => selected.has(row.city.uuid)).length;
  const pageAllSelected = pageRows.length > 0 && pageSelected === pageRows.length;

  const toggle = (uuid: string, on: boolean) =>
    setSelected((current) => {
      const next = new Set(current);
      if (on) next.add(uuid);
      else next.delete(uuid);
      return next;
    });

  const toggleAll = (on: boolean) =>
    setSelected(on ? new Set(rows.map((row) => row.city.uuid)) : new Set());

  const togglePage = (on: boolean) =>
    setSelected((current) => {
      const next = new Set(current);
      for (const row of pageRows) {
        if (on) next.add(row.city.uuid);
        else next.delete(row.city.uuid);
      }
      return next;
    });

  const remove = useMutation({
    mutationFn: (city: City) => deleteCity(city.uuid),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("cities.removed"));
      setRemoving(null);
      void queryClient.invalidateQueries({ queryKey: queryKeys.cities.all });
    },
    // The server refuses a city with districts, and an area captains have been paid for. Its message
    // says which, and it is the answer the operator needs — not a generic failure.
    onError: (err) => toast.error(friendlyErrorMessage(err, t)),
  });

  return (
    <div className="space-y-4">
      <Card>
        <div className="flex flex-wrap items-center justify-between gap-3 border-b p-4">
          <p className="text-sm text-muted-foreground">{t("cities.hint")}</p>
          {canManage ? (
            <div className="flex flex-wrap items-center gap-2">
              {/* A store's list of areas and prices (e.g. the shop's cities export), matched on city + name. */}
              <Button size="sm" variant="outline" onClick={() => setImporting(true)}>
                <FileUp className="me-1.5 h-4 w-4" aria-hidden />
                {t("cities.importCsv")}
              </Button>
              <Button size="sm" onClick={() => setDialog({ kind: "city" })}>
                <Plus className="me-1.5 h-4 w-4" aria-hidden />
                {t("cities.addCity")}
              </Button>
            </div>
          ) : null}
        </div>

        {/*
          What is ticked, and the one thing to do with it. Shown only while something is, and pinned to the bottom of the screen:
          with hundreds of areas the rows being ticked are far below the top of the table.
        */}
        {canManage && selectedAreas.length > 0 ? (
          <div className="fixed inset-x-4 bottom-4 z-40 mx-auto flex max-w-xl flex-wrap items-center justify-between gap-3 rounded-xl border bg-card px-4 py-3 shadow-lg">
            <span className="text-sm font-medium">
              {t("cities.bulk.selected", { count: formatNumber(selectedAreas.length, locale) })}
            </span>
            <div className="flex items-center gap-2">
              <Button size="sm" variant="ghost" onClick={() => toggleAll(false)}>
                <X className="me-1 h-3.5 w-3.5" aria-hidden />
                {t("cities.bulk.clearSelection")}
              </Button>
              <Button size="sm" onClick={() => setBulkEditing(true)}>
                <Pencil className="me-1.5 h-3.5 w-3.5" aria-hidden />
                {t("cities.bulk.editSelected")}
              </Button>
            </div>
          </div>
        ) : null}

        {/* Find an area: by name, or by its city. */}
        {rows.length > 0 ? (
          <div className="flex flex-wrap items-center gap-3 border-b px-4 py-3">
            <DebouncedSearch
              value={search}
              onChange={(value) => {
                setSearch(value);
                setPage(1);
              }}
              placeholder={t("cities.searchAreas")}
              ariaLabel={t("cities.searchAreas")}
              className="w-full sm:w-72"
            />
            <Select
              value={cityFilter}
              onValueChange={(value) => {
                setCityFilter(value);
                setPage(1);
              }}
            >
              <SelectTrigger className="w-full sm:w-56" aria-label={t("cities.cityFilter")}>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="all">{t("cities.allCities")}</SelectItem>
                {cities.map((city) => (
                  <SelectItem key={city.uuid} value={city.uuid}>
                    {city.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
        ) : null}

        {isLoading ? (
          <TableSkeleton rows={6} cols={5} />
        ) : isError ? (
          <ErrorState message={friendlyErrorMessage(error, t)} onRetry={() => refetch()} />
        ) : rows.length === 0 ? (
          <EmptyState title={t("cities.empty")} description={t("cities.emptyHint")} icon={Map} />
        ) : shown.length === 0 ? (
          <EmptyState title={t("cities.noMatch")} description={t("cities.noMatchHint")} icon={Map} />
        ) : (
          <>
          <div className="overflow-x-auto">
            <Table>
              <TableHeader>
                <TableRow>
                  {canManage ? (
                    <TableHead className="w-10">
                      <Checkbox
                        checked={pageAllSelected ? true : pageSelected > 0 ? "indeterminate" : false}
                        onCheckedChange={(checked) => togglePage(checked === true)}
                        aria-label={t("cities.bulk.selectPage")}
                      />
                    </TableHead>
                  ) : null}
                  <TableHead>{t("cities.area")}</TableHead>
                  <TableHead className="text-end">{t("cities.earning")}</TableHead>
                  <TableHead className="text-end">{t("cities.freelanceShare")}</TableHead>
                  <TableHead>{t("cities.rateSource")}</TableHead>
                  <TableHead>{t("cities.reach")}</TableHead>
                  <TableHead aria-label={t("common.actions")} />
                </TableRow>
              </TableHeader>
              <TableBody>
                {pageRows.map(({ city, depth, parent }) => {

                  return (
                    <TableRow
                      key={city.uuid}
                      className={city.is_active ? undefined : "opacity-60"}
                      data-state={selected.has(city.uuid) ? "selected" : undefined}
                    >
                      {canManage ? (
                        <TableCell className="w-10">
                          <Checkbox
                            checked={selected.has(city.uuid)}
                            onCheckedChange={(checked) => toggle(city.uuid, checked === true)}
                            aria-label={t("cities.bulk.selectArea", { name: city.name })}
                          />
                        </TableCell>
                      ) : null}
                      <TableCell>
                        <span
                          className={
                            depth === 1 ? "flex items-center gap-2 ps-6" : "flex items-center gap-2"
                          }
                        >
                          {depth === 1 ? (
                            <MapPin className="h-3.5 w-3.5 text-muted-foreground" aria-hidden />
                          ) : (
                            <Building2 className="h-4 w-4 text-muted-foreground" aria-hidden />
                          )}
                          <span className="flex flex-col">
                            <span className={depth === 0 ? "whitespace-nowrap font-medium" : "whitespace-nowrap"}>
                              {city.name}
                            </span>
                            {/* A district whose city is not on this page (or was searched away) says which city it is in. */}
                            {parent !== null && !onPage.has(parent.uuid) ? (
                              <span className="whitespace-nowrap text-xs text-muted-foreground">
                                {t("cities.inCity", { name: parent.name })}
                              </span>
                            ) : null}
                          </span>
                          {!city.is_active ? (
                            <StatusBadge variant="secondary">{t("cities.inactive")}</StatusBadge>
                          ) : null}
                        </span>
                      </TableCell>

                      <TableCell className="numeric text-end font-medium">
                        {formatCurrency(city.delivery_earning, currency, locale)}
                      </TableCell>

                      {/*
                        What a freelancer keeps of the figure beside it. Shown as a percentage and
                        then as money, because the percentage is the decision and the money is what
                        a captain will ask about.
                      */}
                      <TableCell className="numeric whitespace-nowrap text-end">
                        <span className="font-medium">
                          {formatNumber(Math.round(city.freelance_share * 10000) / 100, locale)}%
                        </span>
                        <span className="ms-2 text-xs text-muted-foreground">
                          {formatCurrency(
                            city.delivery_earning * city.freelance_share,
                            currency,
                            locale,
                          )}
                        </span>
                      </TableCell>

                      <TableCell>
                        {/*
                          The field the whole screen turns on. "Follows the city" is informational;
                          "its own rate" is a decision a later city edit must ask about, and it reads
                          differently on purpose.
                        */}
                        <StatusBadge
                          variant={city.earning_source === "own" ? "info" : "secondary"}
                          >
                          {city.earning_source === "own"
                            ? t("cities.source.own")
                            : t("cities.source.inherited")}
                        </StatusBadge>
                      </TableCell>

                      <TableCell className="text-xs text-muted-foreground">
                        {city.has_location && city.radius_m !== null ? (
                          t("cities.radiusValue", {
                            km: formatNumber(
                              Math.round((city.radius_m / 1000) * 10) / 10,
                              locale,
                            ),
                          })
                        ) : (
                          // Brought in by the import with a name and a price only: no delivery lands here yet.
                          <span title={t("cities.notOnMap")}>
                            <StatusBadge variant="warning">{t("cities.notOnMapShort")}</StatusBadge>
                          </span>
                        )}
                        {depth === 0 && (city.districts_with_own_earning ?? 0) > 0 ? (
                          <span className="mt-0.5 block">
                            {t("cities.ownRateCount", {
                              count: formatNumber(city.districts_with_own_earning ?? 0, locale),
                            })}
                          </span>
                        ) : null}
                      </TableCell>

                      <TableCell>
                        <div className="flex items-center justify-end gap-2">
                          {canManage ? (
                            <>
                              {depth === 0 ? (
                                <Button
                                  size="sm"
                                  variant="ghost"
                                  onClick={() => setDialog({ kind: "district", parent: city })}
                                >
                                  <Plus className="me-1 h-3.5 w-3.5" aria-hidden />
                                  {t("cities.addDistrict")}
                                </Button>
                              ) : null}
                              <Button
                                size="sm"
                                variant="outline"
                                onClick={() => setDialog({ kind: "edit", city, parent })}
                              >
                                {t("common.edit")}
                              </Button>
                              <Button
                                size="sm"
                                variant="ghost"
                                aria-label={t("cities.remove")}
                                onClick={() => setRemoving(city)}
                              >
                                <Trash2 className="h-4 w-4 text-destructive" aria-hidden />
                              </Button>
                            </>
                          ) : null}
                        </div>
                      </TableCell>
                    </TableRow>
                  );
                })}
              </TableBody>
            </Table>
          </div>
          <Pagination
            page={currentPage}
            totalPages={totalPages}
            records={shown.length}
            perPage={AREAS_PER_PAGE}
            onPageChange={setPage}
          />
          </>
        )}
      </Card>

      {dialog ? (
        <CityDialog
          open
          onOpenChange={(open) => {
            if (!open) setDialog(null);
          }}
          mode={dialog}
          currency={currency}
        />
      ) : null}

      {bulkEditing && selectedAreas.length > 0 ? (
        <BulkEditCitiesDialog
          open
          onOpenChange={(open) => {
            if (!open) setBulkEditing(false);
          }}
          areas={selectedAreas}
          currency={currency}
          onSaved={() => toggleAll(false)}
        />
      ) : null}

      {importing ? (
        <ImportCitiesDialog
          open
          onOpenChange={(open) => {
            if (!open) setImporting(false);
          }}
          currency={currency}
        />
      ) : null}

      {removing ? (
        <ConfirmDialog
          open
          onOpenChange={(open) => {
            if (!open) setRemoving(null);
          }}
          title={t("cities.removeTitle")}
          description={t("cities.removeBody", { name: removing.name })}
          confirmLabel={t("cities.remove")}
          destructive
          loading={remove.isPending}
          onConfirm={() => remove.mutate(removing)}
        />
      ) : null}
    </div>
  );
}
