import type { FieldLayout, FieldsJoinMapping, JoinTable, Record, RecordForParameter, SavedData,StringValue, WhereCondition } from "@/types/model";
import { type OneOf, isType } from "./field-types-mobile";
import type { ConditionValue } from "./conditions";
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
export class KintoneIndexEventHandler {
  private config: SavedData<FieldLayout>;
  private currentApp: string;
  constructor(config: SavedData<FieldLayout>, currentApp: string) {
    this.config = config;
    this.currentApp = currentApp;
  }

  public init(): void {
    this.addButtonToView();
  }

  // ボタン追加
  private addButtonToView(): void {
    const headerSpace =kintone.mobile.app.getHeaderSpaceElement();
    if (!headerSpace) {
      throw new Error('このページではヘッダー要素が利用できません。');
    };

    // ボタン追加
    if (document.getElementById('btn-data-fetch')) return;
    const kuc = Kucs['1.18.0'];
    const button = new kuc.MobileButton({
      text: this.config.buttonName,
      type: "submit",
      id: 'btn-data-fetch',
    });
    button.addEventListener('click', () => this.handleButtonClick());
    headerSpace.appendChild(button);
  }

  // ボタンクリック
  private handleButtonClick = async (): Promise<void> => {
    const spinner = this.showSpinner();
    try {
      console.log('データ収集開始...');
      await this.execDataFectch();
      spinner.close();
      location.reload();
    } catch (error) {
      spinner.close();
      const detailError = (error instanceof Error) ? "\n詳細:" + error.message : "";
      const errorMsg = `データ収集中処理中例外発生しました。${detailError}`;
      console.error(errorMsg, error);
      window.alert(errorMsg);
    }
  }

  private showSpinner() {
    const kuc = Kucs['1.18.0'];
    const spinner = new kuc.Spinner({
      text: 'データ収集中',
      container: document.body
    });
    spinner.open();
    return spinner;
  }

  /**
   * 検索データ取得＆作成処理
   */
  private execDataFectch = async (): Promise<void> => {
    const mainTable = this.config.joinTables[0];
    const mainRecords = await this.fetchDataFromApp(mainTable);
    // フィールド結合
    let mainData = mainRecords.map((record) => {
      const rightRecord: Record = {};
      mainTable.fieldsMapping.forEach((mapping => {
        rightRecord[this.fieldCode(mapping.rightField)] = record[this.fieldCode(mapping.leftField)]
      }));
      return rightRecord;
    });
    const joinTables = this.config.joinTables.filter((table, index) => index > 0);
    for (const table of joinTables) {
      const subDatas = await this.fetchDataFromApp(table);
      mainData = this.leftJoin(mainData, subDatas, table);
      // console.log("LeftJoin", mainData);
    };
    //現在のデータをクリアする
    await this.deleteCurrentRecords();
    //データを更新する
    await this.saveDataToCurrentApp(mainData);
  }
  /**
   * Appからデータを取得する
   * @param joinTable 対象アプリかられレコードを取得する
   * @returns 
   */
  private fetchDataFromApp = async (joinTable: JoinTable<FieldLayout>): Promise<Record[]> => {
    // Filter　条件作成
    const filter = this.getWhereCondition(joinTable.whereConditions);
    //取得列を設定する
    const fetchFields = joinTable.fieldsMapping.map(map => this.fieldCode(map.leftField));
    if (joinTable.table) {
      fetchFields.push(joinTable.table);
    }
    const onFields =joinTable.onConditions.map(cond=>this.fieldCode(cond.leftField));
    onFields.forEach(fld=>{
      if(!fetchFields.includes(fld)){
        fetchFields.push(fld);
      }
    });
    // KintoneRESTAPI
    const client = new KintoneRestAPIClient();
    const records = await client.record.getAllRecords({
      app: joinTable.app,
      fields: fetchFields,
      condition: filter
    });
    //console.log("Data Fetch", records);
    //SubTableが含まれる場合、フラットなデータに変換する
    return this.convertToFlatDatas(records, joinTable.table);

  }
  /**
   * 絞り込み条件式作成
   * @param whereCondifions 
   * @returns 
   */
  private getWhereCondition(whereCondifions: WhereCondition<FieldLayout>[]): string {
    const conds = whereCondifions
      .filter((cond) => this.fieldCode(cond.field) !== '');
    const condition = conds.map((cond) => {
      let condition = cond.condition;
      if ("subField" in cond.field && cond.field.subField) {
        condition = this.mapConditionForSubField(cond.condition);
      }
      const condValue = this.getConditionValue(cond.field as OneOf, condition, cond.data);
      return `${this.fieldCode(cond.field)} ${condition} ${condValue}`;
    }).join(' and ');
    return condition;
  }
  /**
   * サブフィールドの演算子対応
   * @param condition 
   * @returns 
   */
  private mapConditionForSubField(condition: ConditionValue): ConditionValue {
    switch (condition) {
      case "=":
        return "in";
      case "!=":
        return "not in";
      default:
        return condition; // 既存の条件をそのまま使用
    }
  }
  /**
   * 条件比較値を変換する
   * @param field 
   * @param condition 
   * @param value 
   * @returns 
   */
  private getConditionValue(field: OneOf, condition: ConditionValue, value: StringValue): string {
    if (!value) return "\"\"";
    if(this.isStringArray(value)){
      //マルチデータの場合
      const items = (value as string[]).map(item => `"${item.trim()}"`);
      return `(${items.join(",")})`;
    }
    const data = value as string;
    if (isType.NUMBER(field) || isType.RECORD_NUMBER(field)) {
      // For numbers, return as is
      return data;
    } else if (isType.DATE(field)) {
      // If field is DATE, format as "yyyy-MM-dd" unless it's a reserved function
      if (/^\d{4}-\d{2}-\d{2}$/.test(data)){
        return `"${data}"`;
      }else if(data.match(/^\w+\(.*\)$/)){
        return data;
      }
      const date = new Date(data);
      return `"${date.toISOString().split('T')[0]}"`;
    } else if (isType.DATETIME(field) || isType.CREATED_TIME(field) || isType.UPDATED_TIME(field)) {
      // 関数を使用する場合
      if (data.match(/^\w+\(.*\)$/)) {
        return data;
      }
      const dateTime = new Date(data);
      return `"${dateTime.toISOString()}"`;
    } else if ((condition === "in" || condition === "not in")) {
      if (data.includes(",")) {
        // Handle "in" and "not in" with comma-separated strings
        const items = data.split(",").map(item => `"${item.trim()}"`);
        return `(${items.join(",")})`;
      } else {
        return `("${data}")`;
      }
    } else {
      // Default case for other types (treat as text)
      return `"${data}"`;
    }
  }

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

  /**
   * fieldからコードを取得する
   * @param field 
   * @returns 
   */
  private fieldCode(field: any): string {
    if (!field) {
      return "";
    }
    if (typeof field === 'string' && field) {
      return field;
    } else if (typeof field === 'object' && 'code' in field) {
      return field.code;
    }
    return "";
  }

  /**
   * ネストされたサブテーブルデータをフラットなデータに変換し、親レコードを複製します。
   * @param records レコードの配列
   * @returns 変換後のフラットなレコード配列
   */
  private convertToFlatDatas(records: Record[], subTable: string): Record[] {
    if (!subTable) {
      return records;
    }
    const flattenedData: Record[] = [];
    records.forEach((record) => {
      // テーブルフィールドが存在するかを確認
      if (record[subTable]?.type === "SUBTABLE" && record[subTable].value.length > 0) {
        // サブテーブル内の各レコードを処理
        record[subTable].value.forEach((nested: Record) => {
          // 親レコードのコピーを作成
          const flatRecord = { ...record };

          // サブテーブルフィールドを抽出してフラットな構造に追加
          Object.entries(nested.value).forEach(([key, field]) => {
            flatRecord[key] = { value: field.value, type: field.type };
          });

          // テーブルフィールドを削除
          delete flatRecord[subTable];

          // 結果の配列に追加
          flattenedData.push(flatRecord);
        });
      } else {
        // サブテーブルが空の場合、親レコードをそのまま追加
        const flatRecord = { ...record };
        delete flatRecord[subTable];
        flattenedData.push(flatRecord);
      }
    });
    // console.log("FlatDatas=>", flattenedData);
    return flattenedData;
  }

  /**
   * データLeftJoin処理
   * @param mainData 
   * @param subData 
   * @param onConditions 
   * @param fieldsMapping 
   * @returns 
   */
  private leftJoin(
    mainData: Record[],
    subData: Record[],
    joinTable: JoinTable<FieldLayout>
  ): Record[] {
    const joinedRecords: Record[] = [];
    mainData.forEach((mainRecord) => {
      const matchedRecords = subData.filter((subRecord) =>
        joinTable.onConditions.every(
          (cond) => mainRecord[this.fieldCode(cond.rightField)]?.value === subRecord[this.fieldCode(cond.leftField)]?.value
        )
      );

      // マッチ出来ない場合、LEFTの列のみ返す
      if (!matchedRecords || matchedRecords.length==0) {
        joinedRecords.push(mainRecord);
      } else {
        matchedRecords.forEach((matchedRecord) => {
          // フィールド結合
          const combinedRecord: Record = { ...mainRecord };
          joinTable.fieldsMapping.forEach((mapping) => {
            combinedRecord[this.fieldCode(mapping.rightField)] = matchedRecord[this.fieldCode(mapping.leftField)];
          });
          joinedRecords.push(combinedRecord);
        });
      }
    });
    return joinedRecords;
  }

  /**
   * 現在アプリのすべてレコードを削除する
   */
  private async deleteCurrentRecords(): Promise<void> {
    const client = new KintoneRestAPIClient();
    const currentRecords = await client.record.getAllRecords({
      app: this.currentApp,
      fields: ["$id"],
    });
    const deleteRecords = currentRecords.map(record => {
      return { id: record.$id.value as string }
    });
    await client.record.deleteAllRecords({
      app: this.currentApp,
      records: deleteRecords
    });
    client.record.addAllRecords
  }
  /**
   * 結合後のデータを現在のアプリに挿入する
   * @param records 
   */
  private async saveDataToCurrentApp(records: Record[]): Promise<void> {
    try {
      const client = new KintoneRestAPIClient();
      const result = await client.record.addAllRecords({
        app: this.currentApp,
        records: this.convertForUpdate(records)
      });
    } catch (error) {
      console.error('データ作成時エラーが発生しました:', error);
      throw error;
    }
  }

  /**
   * Recordを更新時の形式を変換する
   * @param resords 
   * @returns 
   */
  private convertForUpdate(resords: Record[]): RecordForParameter[] {
    return resords.map((record) =>
      Object.fromEntries(
        Object.entries(record).map(([fieldCode, { value }]) => [fieldCode, { value }])
      )
    );
  }
}