/*!
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { HStack, VStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import type { TFunction } from "i18next";
import type { ReactNode } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { LuPanelRightOpen } from "react-icons/lu";
import { useParams, useSearchParams } from "react-router-dom";

import { useTaskInstanceServiceGetHitlDetails } from "openapi/queries";
import type { HITLDetail } from "openapi/requests/types.gen";
import { DataTable } from "src/components/DataTable";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { ErrorAlert } from "src/components/ErrorAlert";
import { HITLReviewDrawer } from "src/components/HITLReview/HITLReviewDrawer.tsx";
import { StateBadge } from "src/components/StateBadge";
import Time from "src/components/Time";
import { TruncatedText } from "src/components/TruncatedText";
import { IconButton, RouterLink } from "src/components/ui";
import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams";
import { useAdvancedSearchArg } from "src/hooks/useAdvancedSearch";
import { useAutoRefresh, useDocumentTitle } from "src/utils";
import { getHITLState, isHITLPending } from "src/utils/hitl";
import { getTaskInstanceLink } from "src/utils/links";

import { HITLFilters } from "./HITLFilters";

type HITLRow = { row: { original: HITLDetail } };

const {
  BODY_SEARCH,
  CREATED_AT_GTE,
  CREATED_AT_LTE,
  DAG_DISPLAY_NAME_PATTERN,
  MAP_INDEX,
  OFFSET: OFFSET_PARAM,
  RESPONDED_BY_USER_NAME,
  RESPONSE_RECEIVED: RESPONSE_RECEIVED_PARAM,
  SUBJECT_SEARCH,
  TASK_ID_PATTERN,
}: SearchParamsKeysType = SearchParamsKeys;

const HITLReviewDrawerButton = ({
  detail,
  onOpen,
}: {
  readonly detail: HITLDetail;
  readonly onOpen: (detail: HITLDetail) => void;
}) => {
  const { t: translate } = useTranslation("hitl");

  return (
    <IconButton label={translate("review.openReviewDrawer")} onClick={() => onOpen(detail)}>
      <LuPanelRightOpen />
    </IconButton>
  );
};

const useHITLReviewDrawer = () => {
  const [selectedDetail, setSelectedDetail] = useState<HITLDetail | undefined>(undefined);

  const openHITLReviewDrawer = (detail: HITLDetail) => {
    setSelectedDetail(detail);
  };

  const closeHITLReviewDrawer = () => {
    setSelectedDetail(undefined);
  };

  return {
    closeHITLReviewDrawer,
    isHITLReviewDrawerOpen: selectedDetail !== undefined,
    openHITLReviewDrawer,
    selectedDetail,
  };
};

const taskInstanceColumns = ({
  dagId,
  renderHITLReviewDrawerButton,
  runId,
  taskId,
  translate,
}: {
  dagId?: string;
  renderHITLReviewDrawerButton?: (detail: HITLDetail) => ReactNode;
  runId?: string;
  taskId?: string;
  translate: TFunction;
}): Array<ColumnDef<HITLDetail>> => [
  {
    accessorKey: "task_instance_state",
    cell: ({ row: { original } }: HITLRow) => (
      <HStack justifyContent="space-between">
        <StateBadge state={original.task_instance.state}>{getHITLState(translate, original)}</StateBadge>
        {renderHITLReviewDrawerButton?.(original)}
      </HStack>
    ),
    header: translate("requiredActionState"),
  },
  {
    accessorKey: "subject",
    cell: ({ row: { original } }: HITLRow) => (
      <RouterLink
        fontWeight="bold"
        onClick={(event) => event.stopPropagation()}
        to={`${getTaskInstanceLink(original.task_instance)}/required_actions`}
      >
        <TruncatedText text={original.subject} />
      </RouterLink>
    ),
    header: translate("subject"),
  },
  ...(Boolean(dagId)
    ? []
    : [
        {
          accessorKey: "task_instance.dag_id",
          cell: ({ row: { original } }: HITLRow) => (
            <RouterLink
              onClick={(event) => event.stopPropagation()}
              to={`/dags/${original.task_instance.dag_id}`}
            >
              <TruncatedText text={original.task_instance.dag_display_name} />
            </RouterLink>
          ),
          enableSorting: false,
          header: translate("common:dagId"),
        },
      ]),
  ...(Boolean(runId)
    ? []
    : [
        {
          accessorKey: "run_id",
          cell: ({ row: { original } }: HITLRow) => (
            <RouterLink
              onClick={(event) => event.stopPropagation()}
              to={`/dags/${original.task_instance.dag_id}/runs/${original.task_instance.dag_run_id}`}
            >
              <TruncatedText text={original.task_instance.dag_run_id} />
            </RouterLink>
          ),
          header: translate("common:dagRunId"),
        },
      ]),
  ...(Boolean(runId)
    ? []
    : [
        {
          accessorKey: "run_after",
          cell: ({ row: { original } }: HITLRow) => <Time datetime={original.task_instance.run_after} />,
          header: translate("common:dagRun.runAfter"),
        },
      ]),
  ...(Boolean(taskId)
    ? []
    : [
        {
          accessorKey: "task_display_name",
          cell: ({ row: { original } }: HITLRow) => (
            <RouterLink
              fontWeight="bold"
              onClick={(event) => event.stopPropagation()}
              to={`${getTaskInstanceLink(original.task_instance)}/required_actions`}
            >
              <TruncatedText text={original.task_instance.task_display_name} />
            </RouterLink>
          ),
          header: translate("common:taskId"),
        },
      ]),
  {
    accessorKey: "rendered_map_index",
    cell: ({ row: { original } }) => <TruncatedText text={original.task_instance.rendered_map_index ?? ""} />,
    header: translate("common:mapIndex"),
  },
  {
    accessorKey: "task_instance_operator",
    cell: ({ row: { original } }) => <TruncatedText text={original.task_instance.operator ?? ""} />,
    header: translate("common:task.operator"),
  },
  {
    accessorKey: "created_at",
    cell: ({ row: { original } }) => <Time datetime={original.created_at} />,
    header: translate("response.created"),
  },
  {
    accessorKey: "responded_by_user_name",
    cell: ({ row: { original } }) => <TruncatedText text={original.responded_by_user?.name ?? ""} />,
    header: translate("response.responded_by_user_name"),
  },
  {
    accessorKey: "responded_at",
    cell: ({ row: { original } }) => <Time datetime={original.responded_at} />,
    header: translate("response.received"),
  },
];

export const HITLTaskInstances = ({
  enableHITLReviewDrawer = false,
}: {
  readonly enableHITLReviewDrawer?: boolean;
}) => {
  const { t: translate } = useTranslation("hitl");
  const { dagId, runId, taskId } = useParams();

  // Only the standalone required-actions page owns the tab title; nested tabs inherit their parent's.
  useDocumentTitle(enableHITLReviewDrawer ? translate("common:browse.requiredActions") : undefined);

  const { closeHITLReviewDrawer, isHITLReviewDrawerOpen, openHITLReviewDrawer, selectedDetail } =
    useHITLReviewDrawer();
  const [searchParams, setSearchParams] = useSearchParams();
  const { setTableURLState, tableURLState } = useTableURLState();
  const { pagination, sorting } = tableURLState;
  const [sort] = sorting;
  const responseReceived = searchParams.get(RESPONSE_RECEIVED_PARAM);

  const baseRefetchInterval = useAutoRefresh({});

  const bodySearch = searchParams.get(BODY_SEARCH) ?? undefined;
  const createdAtGte = searchParams.get(CREATED_AT_GTE) ?? undefined;
  const createdAtLte = searchParams.get(CREATED_AT_LTE) ?? undefined;
  const dagIdPattern = searchParams.get(DAG_DISPLAY_NAME_PATTERN) ?? undefined;
  const taskIdPattern = searchParams.get(TASK_ID_PATTERN) ?? undefined;
  const mapIndexParam = searchParams.get(MAP_INDEX);
  const filterResponseReceived = searchParams.get(RESPONSE_RECEIVED_PARAM) ?? undefined;
  const respondedByUserName = searchParams.get(RESPONDED_BY_USER_NAME) ?? undefined;
  const subjectSearch = searchParams.get(SUBJECT_SEARCH) ?? undefined;

  // Use the filter value if available, otherwise fall back to the old responseReceived param
  const effectiveResponseReceived = filterResponseReceived ?? responseReceived;

  const dagIdArg = useAdvancedSearchArg({
    patternApiKey: "dagIdPattern",
    prefixApiKey: "dagIdPrefixPattern",
    storageKey: DAG_DISPLAY_NAME_PATTERN,
    value: dagIdPattern,
  });
  const taskIdArg = useAdvancedSearchArg({
    patternApiKey: "taskIdPattern",
    prefixApiKey: "taskIdPrefixPattern",
    storageKey: TASK_ID_PATTERN,
    value: taskIdPattern,
  });

  const { data, error, isLoading } = useTaskInstanceServiceGetHitlDetails(
    {
      bodySearch,
      createdAtGte,
      createdAtLte,
      dagId: dagId ?? "~",
      ...dagIdArg,
      dagRunId: runId ?? "~",
      limit: pagination.pageSize,
      mapIndex: mapIndexParam === null ? undefined : parseInt(mapIndexParam, 10),
      offset: pagination.pageIndex * pagination.pageSize,
      orderBy: sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : [],
      respondedByUserName: respondedByUserName === undefined ? undefined : [respondedByUserName],
      responseReceived:
        Boolean(effectiveResponseReceived) && effectiveResponseReceived !== "all"
          ? effectiveResponseReceived === "true"
          : undefined,
      state: effectiveResponseReceived === "false" ? ["deferred", "awaiting_input"] : undefined,
      subjectSearch,
      taskId,
      ...taskIdArg,
    },
    undefined,
    {
      // Only continue auto-refetching when filtering for unreceived responses
      // and at least one TaskInstance is still pending (parked) without a response.
      refetchInterval: (query) => {
        const hasPendingWithoutResponse = Boolean(
          query.state.data?.hitl_details.some(
            (detail: HITLDetail) => detail.responded_at === null && isHITLPending(detail.task_instance.state),
          ),
        );

        return hasPendingWithoutResponse ? baseRefetchInterval : false;
      },
    },
  );

  const handleResponseChange = () => {
    setTableURLState({
      pagination: { ...pagination, pageIndex: 0 },
      sorting,
    });
    searchParams.delete(OFFSET_PARAM);
    setSearchParams(searchParams);
  };

  const columns = taskInstanceColumns({
    dagId,
    renderHITLReviewDrawerButton: enableHITLReviewDrawer
      ? (detail) => <HITLReviewDrawerButton detail={detail} onOpen={openHITLReviewDrawer} />
      : undefined,
    runId,
    taskId,
    translate,
  });

  return (
    <VStack align="start">
      <HITLFilters onResponseChange={handleResponseChange} />
      <DataTable
        columns={columns}
        data={data?.hitl_details ?? []}
        errorMessage={<ErrorAlert error={error} />}
        initialState={tableURLState}
        isLoading={isLoading}
        modelName="hitl:requiredAction"
        onStateChange={setTableURLState}
        total={data?.total_entries}
      />
      {enableHITLReviewDrawer ? (
        <HITLReviewDrawer
          detail={selectedDetail}
          onClose={closeHITLReviewDrawer}
          open={isHITLReviewDrawerOpen}
        />
      ) : null}
    </VStack>
  );
};
