import React, {
  createContext,
  Dispatch,
  ReactNode,
  SetStateAction,
  useCallback,
  useContext,
  useLayoutEffect,
  useMemo,
  useState,
} from 'react'
import {
  Sort,
  Field,
  Select,
  BulkActions,
  ItemActions,
  Fields,
  Filter,
} from '../../utils/common.js'
import { FilterInput } from '../FilterInput/index.js'
import { SortInput } from '../SortInput/index.js'
import { SearchInput } from '../SearchInput/index.js'
import { Menu } from '../Menu/index.js'
import { Button } from '../Button/index.js'
import { Text } from '../Text/index.js'
import { IconButton } from '../IconButton/index.js'
import { useQuery } from '@based/react'
import { Schema, SchemaTypeName } from '@based/schema'
import { generateFinderProps } from '../../utils/heuristics.js'
import { useInfiniteQuery } from '../../hooks/useInfiniteQuery.js'
import {
  Calendar,
  CalendarProps,
  CalendarVariant,
  getCalendarVisiblePeriod,
} from '../Calendar/index.js'
import {
  GridColumnWidth,
  InternalGrid,
  InternalGridProps,
} from '../Grid/index.js'
import {
  Table,
  TableLayout,
  TableProps,
  TableRowHeight,
} from '../Table/index.js'

type FinderView = 'grid' | 'table' | 'calendar'

type BasedFinderRootProps = {
  /** To use the component easily, simply point it to a BasedSchema node to render data. */
  type: SchemaTypeName
  /** A render prop or static content to display inside the Finder component. */
  children: ReactNode | (({ view }: { view: FinderView }) => ReactNode)
  /** As the component is mounted on the screen, you can pass one of three possible values to visualize the data. After the component is rendered, a data analysis will automatically determine the available visualization types. */
  defaultView?: FinderView
  /** Set all the possible values to visualize the data. */
  possibleViews?: FinderView[]
  /** Receives an array of objects containing ‘label’, ‘icon’, ‘color’, and an ‘onClick’ function that can be invoked by the user when selecting a single item. */
  itemActions?: ItemActions
  /** Similar to 'itemActions', receives an array of objects containing ‘label’, ‘icon’, ‘color’, and an ‘onClick’ function that can be invoked by the user when selecting multiple items. */
  bulkActions?: BulkActions
  /** Pass a callback as a parameter and receive information about the item the user interacted with. */
  onItemClick?: (item: any) => void
  /** Function that returns the entry point of the data returned by the BasedQuery to be rendered in the component. */
  transformFields?: (generatedFields: Fields) => Fields
  /** The endpoints used to invoke the BasedQuery of each section of the component. */
  endpoints?: {
    query?: string
    totalQuery?: string
    schema?: string
  }
  /** To mark items as selected. */
  select?: Select
  /** To get the selected items. */
  onSelectChange?: (select: Select) => void
}

type BasedFinderContextType = {
  /** The BasedSchema entry point that is used to render the data. */
  schema: Schema
  /** An object that defines all the fields to be rendered in the component’s active view. These fields are used to match the results from the BasedSchema. */
  fields: Fields
  /** List of disabled field keys. */
  disabledFields: Field['key'][]
  /** Function to update the disabled fields. */
  setDisabledFields: Dispatch<SetStateAction<Field['key'][]>>
  /** The current view type of the Finder (grid, table, or calendar). */
  view: FinderView
  /** Function to change the current view type. */
  setView: Dispatch<SetStateAction<FinderView>>
  /** The selected date in the calendar. */
  calendarDate: number
  /** Function to update the selected calendar date. */
  setCalendarDate: Dispatch<SetStateAction<number>>
  /** The variant of the calendar (e.g., monthly, weekly). */
  calendarVariant: CalendarVariant
  /** Function to update the calendar variant. */
  setCalendarVariant: Dispatch<SetStateAction<CalendarVariant>>
  /** The selected item(s) (optional). */
  select: Select | undefined
  /** Function to update the selected item(s). */
  setSelect: (value: Select) => void
  /** The type of the Finder. */
  type: BasedFinderRootProps['type']
  /** Array of possible views for the Finder (e.g., table, grid, calendar). */
  possibleViews: NonNullable<BasedFinderRootProps['possibleViews']>
  /** Bulk actions available for the Finder. */
  bulkActions?: BasedFinderRootProps['bulkActions']
  /** The current filter applied to the data (optional). */
  filter: Filter | undefined
  /** Function to update the filter. */
  setFilter: Dispatch<SetStateAction<Filter | undefined>>
  /** The current sorting applied to the data (optional). */
  sort: Sort | undefined
  /** Function to update the sort order. */
  setSort: Dispatch<SetStateAction<Sort | undefined>>
  /** The search term (optional). */
  search: string | undefined
  /** Function to update the search term. */
  setSearch: Dispatch<SetStateAction<string | undefined>>
  /** The total number of items available. */
  total: ReturnType<typeof useInfiniteQuery>['total']
  /** Indicator for available scroll. */
  scroll: ReturnType<typeof useInfiniteQuery>['scroll']
  /** Data loaded for the Finder. */
  data: ReturnType<typeof useInfiniteQuery>['data']
  /** Function to handle scrolling behavior. */
  handleScroll: ReturnType<typeof useInfiniteQuery>['handleScroll']
  /** Function to handle calendar-related actions. */
  handleCalendar: ReturnType<typeof useInfiniteQuery>['handleCalendar']
  /** The table layout variant (e.g., regular, showAllColumns). */
  tableLayout: TableLayout
  /** Function to update the table layout. */
  setTableLayout: Dispatch<SetStateAction<TableLayout>>
  /** Actions available for individual items. */
  itemActions?: BasedFinderRootProps['itemActions']
  /** Callback function triggered when an item is clicked. */
  onItemClick?: BasedFinderRootProps['onItemClick']
  /** The height of the visible rows. */
  tableRowHeight: TableRowHeight
  /** Function to update the height of the visible rows programmatically. */
  setTableRowHeight: Dispatch<SetStateAction<TableRowHeight>>
  /** To define the width of all columns (e.g., regular or large). */
  gridColumnWidth: GridColumnWidth
  /** A function to programmatically define the width option of all columns. */
  setGridColumnWidth: Dispatch<SetStateAction<GridColumnWidth>>
} | null

const BasedFinderContext = createContext<BasedFinderContextType>(null)

/**
 * The BasedFinder component enables the visualization of real-time data based on specific BasedQueries and supports displaying information in table, grid, and calendar formats.
 *
 * @param {FinderRootProps} props - The properties for configuring the BasedFinder.
 */
function BasedFinder({
  type,
  bulkActions,
  defaultView = 'table',
  children,
  onItemClick,
  itemActions,
  transformFields,
  endpoints,
  select: selectProp,
  onSelectChange: onSelectChangeProp,
}: BasedFinderRootProps) {
  const [filter, setFilter] = useState<Filter>()
  const [sort, setSort] = useState<Sort>()
  const [search, setSearch] = useState<string>()
  const [select, setSelect] = useState<Select>()
  const [disabledFields, setDisabledFields] = useState<Field['key'][]>([])
  const [calendarDate, setCalendarDate] = useState(Date.now())
  const [calendarVariant, setCalendarVariant] =
    useState<CalendarVariant>('monthly')
  const [tableLayout, setTableLayout] = useState<TableLayout>('regular')
  const [tableRowHeight, setTableRowHeight] =
    useState<TableRowHeight>('regular')
  const [gridColumnWidth, setGridColumnWidth] =
    useState<GridColumnWidth>('regular')

  const { data: schema } = useQuery(endpoints?.schema ?? 'db:schema') as {
    data?: Schema
  }

  const { fields, totalQuery, query, transformQueryResult } = useMemo(() => {
    if (!schema)
      return {
        fields: {},
        totalQuery: () => {},
        query: () => {},
        transformQueryResult: () => {},
      }
    const generated = generateFinderProps(type, schema)

    if (!transformFields) {
      return generated
    }

    return { ...generated, fields: transformFields(generated.fields) }
  }, [schema, type, transformFields])
  const possibleViews = useMemo(() => {
    const res: FinderView[] = ['table']
    const fieldsArray = Object.values(fields)

    if (
      fieldsArray.some((e) => e.calendar?.type === 'start') &&
      fieldsArray.some((e) => e.calendar?.type === 'title')
    ) {
      res.push('calendar')
    }

    if (
      fieldsArray.some((e) => e.grid?.type === 'media') &&
      fieldsArray.some((e) => e.grid?.type === 'title')
    ) {
      res.push('grid')
    }

    return res
  }, [fields])
  const [view, setView] = useState<FinderView>()
  const { data, total, handleScroll, scroll, handleCalendar } =
    useInfiniteQuery({
      endpoints,
      sort,
      filter,
      view,
      query,
      totalQuery,
      transformQueryResult,
    })

  useLayoutEffect(() => {
    setView(possibleViews.includes(defaultView) ? defaultView : 'table')
  }, [defaultView, possibleViews])

  if (!schema || !view) return null

  return (
    <BasedFinderContext.Provider
      value={{
        schema,
        fields,
        disabledFields,
        setDisabledFields,
        filter,
        setFilter,
        sort,
        setSort,
        bulkActions,
        search,
        setSearch,
        select: selectProp ? selectProp : bulkActions ? select : undefined,
        setSelect: onSelectChangeProp
          ? onSelectChangeProp
          : bulkActions
            ? setSelect
            : undefined,
        handleCalendar,
        view,
        setView,
        calendarDate,
        setCalendarDate,
        calendarVariant,
        setCalendarVariant,
        scroll,
        total,
        type,
        possibleViews,
        tableLayout,
        setTableLayout,
        onItemClick,
        itemActions,
        data,
        handleScroll,
        tableRowHeight,
        setTableRowHeight,
        gridColumnWidth,
        setGridColumnWidth,
      }}
    >
      {typeof children === 'function' ? children({ view }) : children}
    </BasedFinderContext.Provider>
  )
}

type FinderHeaderProps = {
  title: string
  /** Optional trailing button to display in the header. */
  trailButton?: ReactNode
}

function FinderHeader({ title, trailButton }: FinderHeaderProps) {
  const ctx = useContext(BasedFinderContext)
  if (!ctx) throw new Error('missing finder ctx')

  const {
    schema,
    view,
    setView,
    calendarDate,
    setCalendarDate,
    calendarVariant,
    total,
    scroll,
    select,
    setSelect,
    type,
    possibleViews,
    bulkActions,
    filter,
    setFilter,
    sort,
    setSort,
    search,
    setSearch,
    handleCalendar,
    tableLayout,
    setTableLayout,
    setCalendarVariant,
    tableRowHeight,
    setTableRowHeight,
    gridColumnWidth,
    setGridColumnWidth,
  } = ctx

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        gap: 12,
        width: '100%',
      }}
    >
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'flex-start',
        }}
      >
        <Text variant="heading-bold">{title}</Text>
      </div>
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'flex-end',
          gap: 12,
        }}
      >
        {view === 'calendar' ? (
          <Calendar.Controls
            value={calendarDate}
            variant={calendarVariant}
            onChange={(value) => {
              setCalendarDate(value)
              const { start, end } = getCalendarVisiblePeriod(
                value,
                calendarVariant,
              )
              handleCalendar(start, end)
            }}
          />
        ) : (
          <>
            {scroll && typeof total === 'number' && (
              <div style={{ flexShrink: 0 }}>
                <Text variant="display-regular" color="neutral60">
                  {`${scroll.realFirst} - ${Math.min(scroll.realLast, total)} of ${total}`}
                </Text>
              </div>
            )}
            {bulkActions && !!select?.length && (
              <Menu>
                <Menu.Trigger>
                  <Button trailIcon="chevron-down">
                    {select.length} selected
                  </Button>
                </Menu.Trigger>
                <Menu.Items>
                  {bulkActions.map((action) => (
                    <Menu.Item
                      leadIcon={action.icon}
                      onClick={() => {
                        action.onClick(select, { setSelect })
                      }}
                      color={action.color}
                    >
                      {action.label}
                    </Menu.Item>
                  ))}
                </Menu.Items>
              </Menu>
            )}
            <FilterInput
              schema={schema}
              type={type}
              value={filter}
              onChange={setFilter}
            />
            <SortInput
              schema={schema}
              type={type}
              value={sort}
              onChange={setSort}
            />
            <SearchInput value={search} onChange={setSearch} />
          </>
        )}
        <Menu>
          <Menu.Trigger>
            {({ open }) => <IconButton icon="sheet" forceHover={open} />}
          </Menu.Trigger>
          <Menu.Items>
            <Menu.OptionCardGroup
              value={view}
              onChange={(v) => {
                if (v === 'calendar') {
                  const { start, end } = getCalendarVisiblePeriod(
                    calendarDate,
                    'monthly',
                  )
                  handleCalendar(start, end)
                }

                setView(v as FinderView)
              }}
              options={[
                {
                  label: 'Table',
                  value: 'table' as const,
                  icon: 'sheet' as const,
                },
                {
                  label: 'Grid',
                  value: 'grid' as const,
                  icon: 'gallery' as const,
                },
                {
                  label: 'Calendar',
                  value: 'calendar' as const,
                  icon: 'date' as const,
                },
              ].filter((e) => possibleViews.includes(e.value))}
            />
            {view === 'table' && (
              <>
                {view === 'table' && (
                  <>
                    <Menu.Header>Layout</Menu.Header>
                    <Menu.ToggleItem
                      value={tableLayout === 'regular'}
                      onChange={() => {
                        setTableLayout('regular')
                      }}
                    >
                      Fit screen
                    </Menu.ToggleItem>
                    <Menu.ToggleItem
                      value={tableLayout === 'showAllColumns'}
                      onChange={() => {
                        setTableLayout('showAllColumns')
                      }}
                    >
                      Horizontal scroll
                    </Menu.ToggleItem>
                    <Menu.Header>Row height</Menu.Header>
                    <Menu.ToggleItem
                      value={tableRowHeight === 'regular'}
                      onChange={() => {
                        setTableRowHeight('regular')
                      }}
                    >
                      Regular
                    </Menu.ToggleItem>
                    <Menu.ToggleItem
                      value={tableRowHeight === 'large'}
                      onChange={() => {
                        setTableRowHeight('large')
                      }}
                    >
                      Large
                    </Menu.ToggleItem>
                    <Menu.Separator />
                  </>
                )}
                <VisibleFieldsMenu />
              </>
            )}
            {view === 'grid' && (
              <>
                <Menu.Header>Column width</Menu.Header>
                <Menu.ToggleItem
                  value={gridColumnWidth === 'regular'}
                  onChange={() => {
                    setGridColumnWidth('regular')
                  }}
                >
                  Regular
                </Menu.ToggleItem>
                <Menu.ToggleItem
                  value={gridColumnWidth === 'large'}
                  onChange={() => {
                    setGridColumnWidth('large')
                  }}
                >
                  Large
                </Menu.ToggleItem>
              </>
            )}
            {view === 'calendar' && (
              <>
                <Menu.Header>Timescale</Menu.Header>
                <Menu.ToggleItem
                  value={calendarVariant === 'monthly'}
                  onChange={() => {
                    setCalendarVariant('monthly')
                    const { start, end } = getCalendarVisiblePeriod(
                      calendarDate,
                      'monthly',
                    )
                    handleCalendar(start, end)
                  }}
                >
                  Month
                </Menu.ToggleItem>
                <Menu.ToggleItem
                  value={calendarVariant === '2-weekly'}
                  onChange={() => {
                    setCalendarVariant('2-weekly')
                    const { start, end } = getCalendarVisiblePeriod(
                      calendarDate,
                      '2-weekly',
                    )
                    handleCalendar(start, end)
                  }}
                >
                  Two week
                </Menu.ToggleItem>
                <Menu.ToggleItem
                  value={calendarVariant === 'weekly'}
                  onChange={() => {
                    setCalendarVariant('weekly')
                    const { start, end } = getCalendarVisiblePeriod(
                      calendarDate,
                      'weekly',
                    )
                    handleCalendar(start, end)
                  }}
                >
                  Week
                </Menu.ToggleItem>
              </>
            )}
          </Menu.Items>
        </Menu>
        {trailButton}
      </div>
    </div>
  )
}

function VisibleFieldsMenu() {
  const ctx = useContext(BasedFinderContext)
  if (!ctx) throw new Error('missing finder ctx')
  const { fields, disabledFields, setDisabledFields } = ctx

  return (
    <Menu>
      <Menu.TriggerItem leadIcon="columns">
        Visible fields ({Object.keys(fields).length - disabledFields.length})
      </Menu.TriggerItem>
      <Menu.Items>
        <Menu.Header>Visible fields</Menu.Header>
        {Object.values(fields).map((field, index) => (
          <Menu.ToggleItem
            key={index}
            value={!disabledFields.includes(field.key)}
            onChange={() => {
              setDisabledFields((prev) =>
                prev.includes(field.key)
                  ? prev.filter((e) => e !== field.key)
                  : [...prev, field.key],
              )
            }}
          >
            {field?.table?.title ?? field.key}
          </Menu.ToggleItem>
        ))}
      </Menu.Items>
    </Menu>
  )
}

type FinderBodyProps = {
  /** Determines if the table header has a transparent background. */
  tableHeaderTransparentBackground?: TableProps['headerTransparentBackground']
  /** Padding for the calendar component. */
  calendarPadding?: CalendarProps['padding']
}

/**
 * The body of the Finder component, rendering different views based on the current state.
 *
 * @param {FinderBodyProps} props - Properties to customize the Finder body.
 */
function FinderBody({
  tableHeaderTransparentBackground,
  calendarPadding,
}: FinderBodyProps) {
  const ctx = useContext(BasedFinderContext)
  if (!ctx) throw new Error('missing finder ctx')
  const {
    fields,
    view,
    data,
    total,
    handleScroll,
    onItemClick,
    bulkActions,
    tableLayout,
    itemActions,
    select,
    setSelect,
    calendarDate,
    calendarVariant,
    sort,
    setSort,
    setDisabledFields,
    tableRowHeight,
    disabledFields,
    gridColumnWidth,
  } = ctx

  const tableFields = useMemo(
    () =>
      Object.fromEntries(
        Object.entries(fields).filter(([key]) => !disabledFields.includes(key)),
      ),
    [fields, disabledFields],
  )

  const onHideField = useCallback(
    ((key) => {
      setDisabledFields((prev) => [...prev, key])
    }) satisfies TableProps['onHideField'],
    [],
  )

  if (view === 'table') {
    return (
      <Table
        fields={tableFields}
        data={data}
        total={total}
        itemActions={itemActions}
        sort={sort}
        select={select}
        rowHeight={tableRowHeight}
        layout={tableLayout}
        onSelectChange={setSelect}
        onScroll={handleScroll}
        onItemClick={onItemClick}
        onSortChange={setSort}
        onHideField={onHideField}
      />
    )
  }

  if (view === 'grid') {
    return (
      <InternalGrid
        virtualized
        fields={fields}
        data={data}
        totalCount={total}
        onScroll={handleScroll}
        onItemClick={onItemClick}
        select={select}
        onSelectChange={setSelect}
        itemActions={itemActions}
        columnWidth={gridColumnWidth}
      />
    )
  }

  if (view === 'calendar') {
    return (
      <Calendar
        fields={fields}
        data={data}
        visiblePeriodStart={
          getCalendarVisiblePeriod(calendarDate, calendarVariant).start
        }
        variant={calendarVariant}
        onItemClick={onItemClick}
        padding={calendarPadding}
      />
    )
  }
}

BasedFinder.Header = FinderHeader
BasedFinder.Body = FinderBody

export { BasedFinder }
export type { BasedFinderRootProps, FinderHeaderProps, FinderView }
