import { computed, obx } from '@ali/lowcode-editor-core';
import { PropsMap, PropsList, CompositeValue } from '@ali/lowcode-types';
import { uniqueId } from '@ali/lowcode-utils';
import { PropStash } from './prop-stash';
import { Prop, IPropParent, UNSET } from './prop';
import { Node } from '../node';
import { TransformStage } from '../transform-stage';

export const EXTRA_KEY_PREFIX = '___';

export class Props implements IPropParent {
  readonly id = uniqueId('props');
  @obx.val private items: Prop[] = [];
  @computed private get maps(): Map<string, Prop> {
    const maps = new Map();
    if (this.items.length > 0) {
      this.items.forEach(prop => {
        if (prop.key) {
          maps.set(prop.key, prop);
        }
      });
    }
    return maps;
  }

  get props(): Props {
    return this;
  }

  private stash = new PropStash(this, prop => {
    this.items.push(prop);
    prop.parent = this;
  });

  /**
   * 元素个数
   */
  @computed get size() {
    return this.items.length;
  }

  @obx type: 'map' | 'list' = 'map';

  constructor(readonly owner: Node, value?: PropsMap | PropsList | null, extras?: object) {
    if (Array.isArray(value)) {
      this.type = 'list';
      this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
    } else if (value != null) {
      this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
    }
    if (extras) {
      Object.keys(extras).forEach(key => {
        this.items.push(new Prop(this, (extras as any)[key], EXTRA_KEY_PREFIX + key));
      });
    }
  }

  import(value?: PropsMap | PropsList | null, extras?: object) {
    this.stash.clear();
    const originItems = this.items;
    if (Array.isArray(value)) {
      this.type = 'list';
      this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
    } else if (value != null) {
      this.type = 'map';
      this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
    } else {
      this.type = 'map';
      this.items = [];
    }
    if (extras) {
      Object.keys(extras).forEach(key => {
        this.items.push(new Prop(this, (extras as any)[key], EXTRA_KEY_PREFIX + key));
      });
    }
    originItems.forEach(item => item.purge());
  }

  merge(value: PropsMap) {
    Object.keys(value).forEach(key => {
      this.query(key, true)!.setValue(value[key]);
    });
  }

  export(stage: TransformStage = TransformStage.Save): { props?: PropsMap | PropsList; extras?: object } {
    if (this.items.length < 1) {
      return {};
    }
    let props: any = {};
    const extras: any = {};
    if (this.type === 'list') {
      props = [];
      this.items.forEach(item => {
        let value = item.export(stage);
        if (value === UNSET) {
          value = null;
        }
        let name = item.key as string;
        if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
          name = name.substr(EXTRA_KEY_PREFIX.length);
          extras[name] = value;
        } else {
          props.push({
            spread: item.spread,
            name,
            value,
          });
        }
      });
    } else {
      this.items.forEach(item => {
        let name = item.key as string;
        if (name == null) {
          // todo ...spread
          return;
        }
        let value = item.export(stage);
        if (value === UNSET) {
          value = null;
        }
        if (typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
          name = name.substr(EXTRA_KEY_PREFIX.length);
          extras[name] = value;
        } else {
          props[name] = value;
        }
      });
    }

    return { props, extras };
  }

  /**
   * 根据 path 路径查询属性
   *
   * @param stash 如果没有则临时生成一个
   */
  query(path: string, stash = true): Prop | null {
    return this.get(path, stash);
    // todo: future support list search
    let matchedLength = 0;
    let firstMatched = null;
    if (this.items) {
      // target: a.b.c
      // trys: a.b.c, a.b, a
      let i = this.items.length;
      while (i-- > 0) {
        const expr = this.items[i];
        if (!expr.key) {
          continue;
        }
        const name = String(expr.key);
        if (name === path) {
          // completely match
          return expr;
        }

        // fisrt match
        const l = name.length;
        if (path.slice(0, l + 1) === `${name}.`) {
          matchedLength = l;
          firstMatched = expr;
        }
      }
    }

    let ret = null;
    if (firstMatched) {
      ret = firstMatched.get(path.slice(matchedLength + 1), true);
    }
    if (!ret && stash) {
      return this.stash.get(path);
    }

    return ret;
  }

  /**
   * 获取某个属性, 如果不存在，临时获取一个待写入
   * @param stash 强制
   */
  get(path: string, stash = false): Prop | null {
    let entry = path;
    let nest = '';
    const i = path.indexOf('.');
    if (i > 0) {
      nest = path.slice(i + 1);
      if (nest) {
        entry = path.slice(0, i);
      }
    }

    const prop = this.maps.get(entry) || (stash && this.stash.get(entry)) || null;

    if (prop) {
      return nest ? prop.get(nest, stash) : prop;
    }

    return null;
  }

  /**
   * 删除项
   */
  delete(prop: Prop): void {
    const i = this.items.indexOf(prop);
    if (i > -1) {
      this.items.splice(i, 1);
      prop.purge();
    }
  }

  /**
   * 删除 key
   */
  deleteKey(key: string): void {
    this.items = this.items.filter(item => {
      if (item.key === key) {
        item.purge();
        return false;
      }
      return true;
    });
  }

  /**
   * 添加值
   */
  add(value: CompositeValue | null, key?: string | number, spread = false): Prop {
    const prop = new Prop(this, value, key, spread);
    this.items.push(prop);
    return prop;
  }

  /**
   * 是否存在 key
   */
  has(key: string): boolean {
    return this.maps.has(key);
  }

  /**
   * 迭代器
   */
  [Symbol.iterator](): { next(): { value: Prop } } {
    let index = 0;
    const items = this.items;
    const length = items.length || 0;
    return {
      next() {
        if (index < length) {
          return {
            value: items[index++],
            done: false,
          };
        }
        return {
          value: undefined as any,
          done: true,
        };
      },
    };
  }

  /**
   * 遍历
   */
  forEach(fn: (item: Prop, key: number | string | undefined) => void): void {
    this.items.forEach(item => {
      return fn(item, item.key);
    });
  }

  /**
   * 遍历
   */
  map<T>(fn: (item: Prop, key: number | string | undefined) => T): T[] | null {
    return this.items.map(item => {
      return fn(item, item.key);
    });
  }

  filter(fn: (item: Prop, key: number | string | undefined) => boolean) {
    return this.items.filter(item => {
      return fn(item, item.key);
    });
  }

  private purged = false;
  /**
   * 回收销毁
   */
  purge() {
    if (this.purged) {
      return;
    }
    this.purged = true;
    this.stash.purge();
    this.items.forEach(item => item.purge());
  }

  getProp(path: string, stash = true): Prop | null {
    return this.query(path, stash as any) || null;
  }

  /**
   * 获取单个属性值
   */
  getPropValue(path: string): any {
    return this.getProp(path, false)?.value;
  }

  /**
   * 设置单个属性值
   */
  setPropValue(path: string, value: any) {
    this.getProp(path, true)!.setValue(value);
  }
}
