import { WhereExpressionBuilder } from 'typeorm';
import { ApiError } from '../../../common/error/api-error';
import { ApiErrorCodes } from '../../../common/error/api-error-codes';
import {
  AssetSelectorQueryBuilding,
  AssetSelectorQueryFieldSelector,
} from '../AssetSelectorQuery';
import {
  AssetPropWhereOp,
  AssetPropWhereOpKind,
  getAssetPropWhereProp,
  AssetPropWhereValue,
} from '../PropsWhere';
import { joinAssetCompsForAssetSelectorQueryFieldAssetComps } from './AssetSelectorQueryFieldAssetComps';
import { AssetSelectorQueryFieldAssetId } from './AssetSelectorQueryFieldAssetId';

export class AssetSelectorQueryFieldTypeIds extends AssetSelectorQueryFieldAssetId {
  constructor() {
    super('typeids', 'ac.type_ids');
  }

  requestProp(qb: AssetSelectorQueryBuilding): string {
    joinAssetCompsForAssetSelectorQueryFieldAssetComps(qb);
    return this.dbField;
  }

  where(
    qb: AssetSelectorQueryBuilding,
    qwhere: WhereExpressionBuilder,
    cond_op: AssetPropWhereOp,
  ): void {
    switch (cond_op.op) {
      case AssetPropWhereOpKind.EQUAL:
      case AssetPropWhereOpKind.EQUAL_NOT:
      case AssetPropWhereOpKind.ANY:
      case AssetPropWhereOpKind.ANY_NOT:
        const param_num = ++qb.namesCounter;
        const oprnd_prop = getAssetPropWhereProp(cond_op.v as any);
        if (oprnd_prop) {
          throw new ApiError(
            'Only constant values can be used in prop',
            ApiErrorCodes.PARAM_BAD_VALUE,
            {
              prop: this.name,
              value: cond_op,
            },
          );
        }

        let val: any;
        let opval_sql: string;
        let not = false;
        switch (cond_op.op) {
          case AssetPropWhereOpKind.EQUAL:
            opval_sql = `&& :val${param_num}`;
            val = [this.prepareFilterValue(cond_op.v as AssetPropWhereValue)];
            break;
          case AssetPropWhereOpKind.EQUAL_NOT:
            opval_sql = `&& :val${param_num}`;
            val = [this.prepareFilterValue(cond_op.v as AssetPropWhereValue)];
            not = true;
            break;
          case AssetPropWhereOpKind.ANY:
            opval_sql = `&& :val${param_num}`;
            val = cond_op.v.map((v) => this.prepareFilterValue(v));
            break;
          case AssetPropWhereOpKind.ANY_NOT:
            opval_sql = `&& :val${param_num}`;
            val = cond_op.v.map((v) => this.prepareFilterValue(v));
            not = true;
            break;
        }

        const prop_ref = this.requestProp(qb);
        qwhere.andWhere(
          `(${
            not ? `${prop_ref} IS NULL OR NOT` : ''
          } (${prop_ref} ${opval_sql}))`,
          {
            [`val${param_num}`]: val,
          },
        );
        break;
      default:
        throw new ApiError(
          `Asset filter type is not supported for prop`,
          ApiErrorCodes.PARAM_BAD_VALUE,
          {
            prop: this.name,
            value: cond_op,
          },
        );
    }
  }

  selectProp(
    qb: AssetSelectorQueryBuilding,
    as: string,
  ): AssetSelectorQueryFieldSelector {
    const ref = this.requestProp(qb);
    return {
      ref,
      as,
      reader: (row, res) => {
        const indices = [];
        if (row[as]) {
          for (let i = 0; i < row[as].length; i++) {
            res[`${as}\\${i}`] = row[as][i];
            indices.push(i);
          }
        }
        res[as] = indices;
      },
    };
  }
}
