import type { FieldsInfo, FieldsJoinMapping, JoinTable, WhereCondition } from '@/types/model';
import {
  client,
  isType,
  type FieldType,
  type App,
  type Layout,
  type OneOf,
  type Properties,
} from './kintone-rest-api-client';
import type { ComboboxItem, DropdownItem } from 'kintone-ui-component';
import { isSpecialType, type SpecialType } from './join';

export const EMPTY_OPTION = {
  value: '',
  label: '--------',
} as DropdownItem;

export function generateId(): string {
  const timestamp = new Date().getTime().toString(36);
  const randomNum = Math.random().toString(36).substring(2, 11);
  return `${timestamp}-${randomNum}`;
}

export function search(list: Array<WhereCondition | FieldsJoinMapping>, id: string) {
  if (!list) return;
  return list.find((item) => item.id === id);
}

export const getEmptyWhereCondition = () =>
  ({ field: '', condition: '', data: '', id: generateId() }) as WhereCondition;
export const getEmptyOnCondition = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping;
export const getEmptyFieldsMapping = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping;

export function createEmptyJoinTable(id = generateId()) {
  return resetTable({ id, app: '' } as JoinTable);
}

export function resetTable(table: JoinTable) {
  table.table = '';
  return resetConditions(table);
}

export function resetConditions(table: JoinTable) {
  table.onConditions = [getEmptyOnCondition()];
  table.fieldsMapping = [getEmptyFieldsMapping()];
  table.whereConditions = [getEmptyWhereCondition()];
  return table;
}

const LIMIT = 100; // 毎回請求の最大値
export const loadApps = async (offset = 0, _apps: DropdownItem[] = []): Promise<DropdownItem[]> => {
  const { apps } = await client.app.getApps({ limit: LIMIT, offset });
  const allApps: DropdownItem[] = [
    ..._apps,
    ...apps.map((app: App) => ({ value: app.appId, label: app.name + '（ID: ' + app.appId + '）' })),
  ];
  if (apps.length === LIMIT) {
    return loadApps(offset + LIMIT, allApps);
  }
  allApps.sort((a, b) => Number(b.value) - Number(a.value));
  allApps.unshift(EMPTY_OPTION);
  return allApps;
};

export const loadAppFieldsAndLayout = async (appId: string | number = kintone.app.getId() as number) => {
  const fields = (await client.app.getFormFields({ app: appId })).properties;
  return {
    fields: flatFields(fields),
    layout: (await client.app.getFormLayout({ app: appId })).layout,
  } as FieldsInfo;
};

function flatFields(fields: Properties) {
  const subtableFields = {} as Properties;
  Object.values(fields).forEach((field) => {
    if (isType.SUBTABLE(field)) {
      Object.values(field.fields).forEach((subField) => {
        const copy = JSON.parse(JSON.stringify(subField)) as typeof subField & {originLabel:string, tableCode:string};
        copy.label = '[' + field.label + '].' + subField.label;
        copy.originLabel = subField.label;
        copy.tableCode = field.code;
        subtableFields[subField.code] = copy;
      });
    }
  });
  return { ...fields, ...subtableFields };
}

type FilterType = Array<FieldType | SpecialType>;
type Param = {
  subTableCode: string | undefined;
  filterType?: FilterType;
  baseFilter: FieldType[] | undefined;
  dependFilterField?: OneOf;
  defaultLabel?: string;
  defaultDisableCallback?: (field: OneOf) => boolean;
  needAllSubTableField?: boolean;
};
export const getFieldsDropdownItems = (
  { fields, layout }: FieldsInfo,
  {
    subTableCode, // specified subTable
    baseFilter,   // set not allowed items hidden, undefined means no filter
    defaultLabel, // label shown (default '--------')
    filterType,   // set not allowed items disabled, undefined and [] means no filter
    dependFilterField, // used for filterType
    defaultDisableCallback, // callback to control disabled items, like filterType
    needAllSubTableField = false, // show all subtable fields
  }: Param,
) => {
  // get used field codes
  const fieldOrder = extractFields(layout, baseFilter, !!needAllSubTableField, subTableCode);
  const fieldMap = fields;

  // create labels
  const labels: ComboboxItem[] = [
    {
      value: EMPTY_OPTION.value,
      label: defaultLabel || EMPTY_OPTION.label,
    },
  ];
  return fieldOrder.reduce((acc, fieldCode) => {
    const field = fieldMap[fieldCode];
    if (!fieldCode) return acc;
    acc.push({
      value: fieldCode,
      label: field.label + '（FC: ' + fieldCode + '）',
      disabled:
        (defaultDisableCallback && defaultDisableCallback(field)) ||
        (filterType && !checkFilterType(field, dependFilterField, filterType)),
    });
    return acc;
  }, labels);
};

const checkFilterType = (field: OneOf, dependFilterField: OneOf | undefined, filterType: FilterType) => {
  if (!filterType.length) return true; // [] means no filter
  return !!filterType.find((type) => {
    if (isSpecialType(type)) {
      return type.check(field, dependFilterField);
    }
    return isType[type](field);
  });
};

export const getTableFieldsDropdownItems = ({ fields }: FieldsInfo, filterType?: FieldType) => {
  return Object.keys(fields).reduce(
    (acc, fieldCode) => {
      const field = fields[fieldCode];
      if (filterType && !isType[filterType](field)) return acc;
      acc.push({
        value: fieldCode,
        label: field.label + '（FC: ' + fieldCode + '）',
      });
      return acc;
    },
    [EMPTY_OPTION],
  );
};

const extractFields = (layout: Layout, baseFilter: FieldType[] | undefined, needAllSubTableField: boolean, subTableCode?: string) => {
  return layout.reduce((acc, each) => {
    if (each.type === 'GROUP') {
      acc.push(...extractFields(each.layout, baseFilter, needAllSubTableField, subTableCode));
    } else if (each.type === 'ROW' || (!needAllSubTableField && each.code === subTableCode) || (needAllSubTableField && each.type === 'SUBTABLE')) {
      acc.push(
        ...each.fields.map((field) => {
          if (!('code' in field)) return '';
          if (!baseFilter) return field.code;
          return baseFilter.find((t) => t === field.type) ? field?.code || '' : '';
        }),
      );
    }
    return acc;
  }, [] as string[]);
};

export function getFieldObj(fieldCode: string, { fields }: FieldsInfo, subTableCode?: string) {
  const meta = getMeta(fields, subTableCode);
  return meta[fieldCode];
}

export function getMeta(fields: Properties, subTableCode?: string, withNoSubTableField = true) {
  if (!fields || !subTableCode) {
    return fields;
  }
  let meta = fields;
  const table = meta[subTableCode];
  if (isType.SUBTABLE(table)) {
    const subFields = table.fields;
    Object.values(subFields).forEach((field) => {
      if (typeof field === 'object' && field !== null) {
        (field as Record<string, any>).subField = true;
      }
    });
    if (withNoSubTableField) {
      meta = { ...fields, ...subFields };
    } else {
      meta = subFields;
    }
  }
  return meta;
}

export const isStringArray = (value: any) => {
  if (Array.isArray(value) && value.every((x) => typeof x === 'string')) {
    return true;
  }
  return false;
};
