import React from 'react';

import { KintoneFormFieldProperty, KintoneRestAPIClient } from '@kintone/rest-api-client';
import { KintoneRecord } from '@shin-chan/kypes/types/page';
import dayjs from 'dayjs';
import Docxtemplater from 'docxtemplater';
import expressionParser from 'docxtemplater/expressions';
import { saveAs } from 'file-saver';
import moize from 'moize';
import PizZip from 'pizzip';
import { useTranslation } from 'react-i18next';
import invariant from 'tiny-invariant';
import { DOCX_CONTENT_TYPE } from '../common/constants';
import { KintoneFormFieldProperties } from '../common/types';
import KintonePluginAlert from '../common/ui/KintonePluginAlert';
import KintonePluginButton from '../common/ui/KintonePluginButton';

interface TemplateData {
  [key: string]: TemplateData | TemplateData[] | string | string[];
}

interface MenuPanelProps {
  pluginId: string;
  event: kintone.events.AppRecordDetailShowEvent | kintone.events.MobileAppRecordDetailShowEvent;
}

const cachedFormFieldsProperties = moize.promise(async (appId: number): Promise<KintoneFormFieldProperties> => {
  const client = new KintoneRestAPIClient();
  const { properties } = await client.app.getFormFields({ app: appId, preview: false });
  return properties;
});

const formatNumberValue = (value: string, digit: boolean, scale?: number): string => {
  if (value !== '' && !isNaN(Number(value))) {
    const numberValue = Number(value);
    const options: Intl.NumberFormatOptions = {
      useGrouping: digit,
    };
    if (scale != null && scale >= 0) {
      options.minimumFractionDigits = scale;
      options.maximumFractionDigits = scale;
    }
    return new Intl.NumberFormat('en-US', options).format(numberValue);
  }
  return value;
};

const formatValueWithUnit = (
  value: string,
  property: KintoneFormFieldProperty.Number | KintoneFormFieldProperty.Calc,
): string => {
  if (value !== '' && property.unit !== '') {
    if (property.unitPosition === 'BEFORE') {
      return `${property.unit} ${value}`;
    } else if (property.unitPosition === 'AFTER') {
      return `${value} ${property.unit}`;
    }
  }
  return value;
};

const formatNumberRecordValue = (
  value: string,
  property: KintoneFormFieldProperty.Number | KintoneFormFieldProperty.Lookup,
): string => {
  if ('lookup' in property) {
    return value;
  }
  const scale = property.displayScale !== '' ? Number(property.displayScale) : undefined;
  const digit = property.digit ?? false;
  const formattedValue = formatNumberValue(value, digit, scale);
  return formatValueWithUnit(formattedValue, property);
};

const formatCalculatedRecordValue = (
  value: string,
  property: KintoneFormFieldProperty.Calc,
  language: string,
): string => {
  const { format } = property;
  if (value === '') {
    return '';
  }
  const scale = property.displayScale !== '' ? Number(property.displayScale) : undefined;
  let formattedValue: string;
  if (format === 'NUMBER') {
    formattedValue = formatNumberValue(value, false, scale);
  } else if (format === 'NUMBER_DIGIT') {
    formattedValue = formatNumberValue(value, true, scale);
  } else if (format === 'DATETIME') {
    // "2025-05-30T00:00:00Z" -> "2025-05-30 9:00" (browser local timezone)
    const date = new Date(value);
    invariant(!isNaN(date.getTime()), `Expected value to be a valid date string, but got ${value}`);
    formattedValue = dayjs(date).format('YYYY-MM-DD h:mm');
  } else if (format === 'DATE') {
    // "2025-05-30T00:00:00Z" -> "2025-05-30" (browser local timezone)
    const date = new Date(value);
    invariant(!isNaN(date.getTime()), `Expected value to be a valid date string, but got ${value}`);
    formattedValue = dayjs(date).format('YYYY-MM-DD');
  } else if (format === 'TIME') {
    // "00:30" -> "0:30"
    const [hours, minutes] = value.split(':').map((v) => Number(v));
    invariant(
      !isNaN(hours) && !isNaN(minutes),
      `Expected hours and minutes to be numbers, but got hours: ${hours}, minutes: ${minutes}`,
    );
    formattedValue = `${hours}:${String(minutes).padStart(2, '0')}`;
  } else if (format === 'HOUR_MINUTE') {
    // "49:30" -> en:"49 hours 30 minutes", ja: "49時間30分"
    const [hours, minutes] = value.split(':').map((v) => Number(v));
    invariant(
      !isNaN(hours) && !isNaN(minutes),
      `Expected hours and minutes to be numbers, but got hours: ${hours}, minutes: ${minutes}`,
    );
    formattedValue = language === 'ja' ? `${hours}時間${minutes}分` : `${hours} hours ${minutes} minutes`;
  } else if (format === 'DAY_HOUR_MINUTE') {
    // "49:30" -> en: "2 days 1 hour 30 minutes", ja: "2日1時間30分
    const [hours, minutes] = value.split(':').map((v) => Number(v));
    invariant(
      !isNaN(hours) && !isNaN(minutes),
      `Expected hours and minutes to be numbers, but got hours: ${hours}, minutes: ${minutes}`,
    );
    const days = Math.floor(Number(hours) / 24);
    const remainingHours = Number(hours) % 24;
    formattedValue =
      language === 'ja'
        ? `${days}日${remainingHours}時間${minutes}分`
        : `${days} days ${remainingHours} hours ${minutes} minutes`;
  } else {
    // DATETIME, DATE, TIME
    formattedValue = value;
  }
  return formatValueWithUnit(formattedValue, property);
};

const record2data = (
  properties: KintoneFormFieldProperties,
  record: Partial<KintoneRecord>,
  language: string,
): TemplateData => {
  const data: TemplateData = {};
  for (const key in record) {
    if (Object.prototype.hasOwnProperty.call(record, key) && Object.prototype.hasOwnProperty.call(properties, key)) {
      const item = record[key];
      const property = properties[key];
      if (item == null) continue;
      const { type, value } = item;
      if (value == null) {
        data[key] = '';
      } else if (type === 'CREATOR' || type === 'MODIFIER') {
        data[key] = {
          name: value.name,
          code: value.code,
        };
      } else if (type === 'NUMBER') {
        invariant(property.type === 'NUMBER', `Expected property type to be NUMBER, but got ${property.type}`);
        data[key] = formatNumberRecordValue(value, property);
      } else if (type === 'CALC') {
        invariant(property.type === 'CALC', `Expected property type to be CALC, but got ${property.type}`);
        data[key] = formatCalculatedRecordValue(value, property, language);
      } else if (type === 'CHECK_BOX' || type === 'MULTI_SELECT' || type === 'CATEGORY') {
        data[key] = value.map((v) => v);
      } else if (
        type === 'USER_SELECT' ||
        type === 'ORGANIZATION_SELECT' ||
        type === 'GROUP_SELECT' ||
        type === 'STATUS_ASSIGNEE'
      ) {
        data[key] = value.map((v) => {
          return { name: v.name, code: v.code };
        });
      } else if (type === 'SUBTABLE') {
        invariant(property.type === 'SUBTABLE', `Expected property type to be SUBTABLE, but got ${property.type}`);
        data[key] = value.map((subRecord) => record2data(property.fields, subRecord.value, language));
      } else if (type === 'FILE') {
        invariant(property.type === 'FILE', `Expected property type to be FILE, but got ${property.type}`);
        data[key] = value.map((file) => ({
          name: file.name,
          size: file.size,
          contentType: file.contentType,
          fileKey: file.fileKey,
        }));
      } else {
        // RECORD_NUMBER, __ID__, __REVISION__, CREATED_TIME, UPDATED_TIME
        // SINGLE_LINE_TEXT, MULTI_LINE_TEXT, RICH_TEXT, RADIO_BUTTON, DROP_DOWN, DATE, TIME, DATETIME, LINK, STATUS
        data[key] = value;
      }
    }
  }
  return data;
};

const MenuPanel: React.FC<MenuPanelProps> = (props) => {
  const { pluginId, event } = props;
  const { t } = useTranslation();
  const appId = event.appId;
  const properties = React.use(cachedFormFieldsProperties(appId));
  const language = kintone.getLoginUser().language;
  const config = kintone.plugin.app.getConfig(pluginId);
  const template: string = config.template ?? '';

  if (template === '') {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-field-is-not-set')}
      </KintonePluginAlert>
    );
  }
  const record = event.record[template];
  if (record == null) {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-field-is-not-available')}
      </KintonePluginAlert>
    );
  }
  if (record.type !== 'FILE') {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-field-must-be-an-attachment-field')}
      </KintonePluginAlert>
    );
  }
  if (record.value.length === 0) {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-field-does-not-contain-any-files')}
      </KintonePluginAlert>
    );
  }
  if (record.value.length > 1) {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-field-contains-multiple-files')}
      </KintonePluginAlert>
    );
  }
  const { fileKey, contentType } = record.value[0];
  if (contentType !== DOCX_CONTENT_TYPE) {
    return (
      <KintonePluginAlert>
        {t('name')}: {t('errors.template-file-must-be-a-docx', { contentType })}
      </KintonePluginAlert>
    );
  }

  const handleOnClickOutputButton = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    const client = new KintoneRestAPIClient();
    client.file
      .downloadFile({ fileKey })
      .then((content) => {
        const zip = new PizZip(content);
        const doc = new Docxtemplater(zip, {
          paragraphLoop: true,
          linebreaks: true,
          parser: expressionParser,
          nullGetter: () => '',
        });
        doc.render(record2data(properties, event.record, language));
        const out = doc.getZip().generate({ type: 'blob', mimeType: DOCX_CONTENT_TYPE });
        saveAs(out, 'output.docx');
      })
      .catch((error) => {
        console.error('Error downloading file:', error);
        alert('Failed to download the Word template file.');
      });
  };

  return (
    <KintonePluginButton variant="normal" onClick={handleOnClickOutputButton}>
      {t('buttons.output')}
    </KintonePluginButton>
  );
};
export default MenuPanel;
