import { type ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
import { type UseFormSetValue, useForm } from 'react-hook-form';

import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { Button, Input, Switch, Field, Label, TextArea, Stack, Alert, Box } from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { getProvisionedMeta } from 'app/features/provisioning/components/utils/getProvisionedMeta';

import { type DashboardScene } from '../scene/DashboardScene';

import { type DashboardChangeInfo, NameAlreadyExistsError, SaveButton, isNameExistsError } from './shared';
import { useSaveDashboard } from './useSaveDashboard';

interface SaveDashboardAsFormDTO {
  firstName?: string;
  title: string;
  description: string;
  folder: { uid?: string; title?: string };
  copyTags: boolean;
}

export interface Props {
  dashboard: DashboardScene;
  changeInfo: DashboardChangeInfo;
}

export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
  const { changedSaveModel } = changeInfo;

  const { register, handleSubmit, setValue, formState, getValues, watch, trigger } = useForm<SaveDashboardAsFormDTO>({
    mode: 'onBlur',
    defaultValues: {
      title: changeInfo.isNew ? changedSaveModel.title! : `${changedSaveModel.title} Copy`,
      description: changedSaveModel.description ?? '',
      folder: {
        uid: dashboard.state.meta.folderUid,
        title: dashboard.state.meta.folderTitle,
      },
      copyTags: false,
    },
  });

  const { errors, isValid } = formState;
  const formValues = watch();

  const { state, onSaveDashboard } = useSaveDashboard(false);

  const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({});

  const validationTimeoutRef = useRef<NodeJS.Timeout>(undefined);

  // Validate title on form mount to catch invalid default values
  useEffect(() => {
    trigger('title');
  }, [trigger]);

  // Cleanup timeout on unmount
  useEffect(() => {
    return () => {
      if (validationTimeoutRef.current) {
        clearTimeout(validationTimeoutRef.current);
      }
    };
  }, []);

  const handleTitleChange = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      setValue('title', e.target.value, { shouldDirty: true });
      if (validationTimeoutRef.current) {
        clearTimeout(validationTimeoutRef.current);
      }
      validationTimeoutRef.current = setTimeout(() => {
        trigger('title');
      }, 400);
    },
    [setValue, trigger]
  );

  const onSave = async (overwrite: boolean) => {
    if (validationTimeoutRef.current) {
      clearTimeout(validationTimeoutRef.current);
    }

    const isTitleValid = await trigger('title');

    // This prevents the race between the new input and old validation state
    if (!isTitleValid) {
      return;
    }

    const data = getValues();

    const result = await onSaveDashboard(dashboard, {
      overwrite,
      folderUid: data.folder.uid,
      rawDashboardJSON: changedSaveModel,

      // save as config
      saveAsCopy: true,
      isNew: changeInfo.isNew,
      copyTags: data.copyTags,
      title: data.title,
      description: data.description,
    });

    if (result.status === 'success') {
      dashboard.closeModal();
    } else {
      setContentSent({
        title: data.title,
        folderUid: data.folder.uid,
      });
    }
  };

  const cancelButton = (
    <Button variant="secondary" onClick={() => dashboard.closeModal()} fill="outline">
      <Trans i18nKey="dashboard-scene.save-dashboard-as-form.cancel-button.cancel">Cancel</Trans>
    </Button>
  );

  const saveButton = (overwrite: boolean) => {
    return <SaveButton isValid={isValid} isLoading={state.loading} onSave={onSave} overwrite={overwrite} />;
  };
  function renderFooter(error?: Error) {
    const formValuesMatchContentSent =
      formValues.title.trim() === contentSent.title && formValues.folder.uid === contentSent.folderUid;
    if (isNameExistsError(error) && formValuesMatchContentSent) {
      return <NameAlreadyExistsError cancelButton={cancelButton} saveButton={saveButton} />;
    }
    return (
      <>
        {error && formValuesMatchContentSent && (
          <Alert
            title={t(
              'dashboard-scene.save-dashboard-as-form.render-footer.title-failed-to-save-dashboard',
              'Failed to save dashboard'
            )}
            severity="error"
          >
            {error.message && <p>{error.message}</p>}
          </Alert>
        )}
        <Stack alignItems="center">
          {cancelButton}
          {saveButton(false)}
        </Stack>
      </>
    );
  }

  return (
    <form onSubmit={handleSubmit(() => onSave(false))}>
      <Stack direction="column" gap={2}>
        <Field
          noMargin
          label={<TitleFieldLabel onChange={setValue} />}
          invalid={!!errors.title}
          error={errors.title?.message}
        >
          <Input
            {...register('title', {
              required: t('dashboard-scene.save-dashboard-as-form.required', 'Required'),
              validate: validateDashboardName,
              onChange: handleTitleChange,
            })}
            aria-label={t(
              'dashboard-scene.save-dashboard-as-form.aria-label-save-dashboard-title-field',
              'Save dashboard title field'
            )}
            data-testid={selectors.components.Drawer.DashboardSaveDrawer.saveAsTitleInput}
          />
        </Field>
        <Field
          noMargin
          label={<DescriptionLabel onChange={setValue} />}
          invalid={!!errors.description}
          error={errors.description?.message}
        >
          <TextArea
            {...register('description', { required: false })}
            aria-label={t(
              'dashboard-scene.save-dashboard-as-form.aria-label-save-dashboard-description-field',
              'Save dashboard description field'
            )}
            autoFocus
          />
        </Field>

        <Field noMargin label={t('dashboard-scene.save-dashboard-as-form.label-folder', 'Folder')}>
          <FolderPicker
            onChange={async (uid: string | undefined, title: string | undefined) => {
              setValue('folder', { uid, title });
              const meta = await getProvisionedMeta(uid);
              dashboard.setState({
                meta: {
                  ...meta,
                  folderUid: uid,
                },
              });
              // Re-validate title when folder changes to check for duplicates in new folder
              trigger('title');
            }}
            value={formValues.folder?.uid}
          />
        </Field>
        {!changeInfo.isNew && (
          <Field noMargin label={t('dashboard-scene.save-dashboard-as-form.label-copy-tags', 'Copy tags')}>
            <Switch {...register('copyTags')} />
          </Field>
        )}
        <Box paddingTop={2}>{renderFooter(state.error)}</Box>
      </Stack>
    </form>
  );
}

export interface TitleLabelProps {
  onChange: UseFormSetValue<SaveDashboardAsFormDTO>;
}

export function TitleFieldLabel(props: TitleLabelProps) {
  return (
    <Stack justifyContent="space-between">
      <Label htmlFor="description">
        <Trans i18nKey="dashboard-scene.title-field-label.title">Title</Trans>
      </Label>
      {/* {config.featureToggles.dashgpt && isNew && (
                <GenAIDashDescriptionButton
                  onGenerate={(description) => field.onChange(description)}
                  dashboard={dashboard}
                />
              )} */}
    </Stack>
  );
}

export interface DescriptionLabelProps {
  onChange: UseFormSetValue<SaveDashboardAsFormDTO>;
}

export function DescriptionLabel(props: DescriptionLabelProps) {
  return (
    <Stack justifyContent="space-between">
      <Label htmlFor="description">
        <Trans i18nKey="dashboard-scene.description-label.description">Description</Trans>
      </Label>
      {/* {config.featureToggles.dashgpt && isNew && (
                <GenAIDashDescriptionButton
                  onGenerate={(description) => field.onChange(description)}
                  dashboard={dashboard}
                />
              )} */}
    </Stack>
  );
}

async function validateDashboardName(title: string, formValues: SaveDashboardAsFormDTO) {
  if (title === formValues.folder.title?.trim()) {
    return 'Dashboard name cannot be the same as folder name';
  }

  try {
    await validationSrv.validateNewDashboardName(formValues.folder.uid ?? 'general', title);
    return true;
  } catch (e) {
    return e instanceof Error ? e.message : 'Dashboard name is invalid';
  }
}
