import { Measure, Constraints, Placeable, LayoutFn, NewPlaceable } from '../bluefish';
import { NewBBoxClass } from '../NewBBox';

export type Alignment2D =
  | 'topLeft'
  | 'topCenter'
  | 'topRight'
  | 'centerLeft'
  | 'center'
  | 'centerRight'
  | 'bottomLeft'
  | 'bottomCenter'
  | 'bottomRight';

// generate a union of single-key objects using Alignment2D as the keys
export type Alignment2DObjs = { [K in Alignment2D]: { [k in K]: boolean } }[Alignment2D];

export type VerticalAlignment = 'top' | 'center' | 'bottom';
export type HorizontalAlignment = 'left' | 'center' | 'right';

export type Alignment1D = 'top' | 'centerVertically' | 'bottom' | 'left' | 'centerHorizontally' | 'right';

export type Alignment1DObjs = { [K in Alignment1D]: { [k in K]: boolean } }[Alignment1D];

export type AlignProps = (Alignment2DObjs | Alignment1DObjs) & { to?: Alignment2D | Alignment1D } & {
  x?: number;
  y?: number;
};

export const splitAlignment = (
  alignment: Alignment2D | Alignment1D,
): [VerticalAlignment | undefined, HorizontalAlignment | undefined] => {
  let verticalAlignment: VerticalAlignment | undefined;
  let horizontalAlignment: HorizontalAlignment | undefined;
  switch (alignment) {
    case 'top':
    case 'topLeft':
    case 'topCenter':
    case 'topRight':
      verticalAlignment = 'top';
      break;
    case 'centerVertically':
    case 'centerLeft':
    case 'center':
    case 'centerRight':
      verticalAlignment = 'center';
      break;
    case 'bottom':
    case 'bottomLeft':
    case 'bottomCenter':
    case 'bottomRight':
      verticalAlignment = 'bottom';
      break;
  }

  switch (alignment) {
    case 'left':
    case 'topLeft':
    case 'centerLeft':
    case 'bottomLeft':
      horizontalAlignment = 'left';
      break;
    case 'centerHorizontally':
    case 'topCenter':
    case 'center':
    case 'bottomCenter':
      horizontalAlignment = 'center';
      break;
    case 'right':
    case 'topRight':
    case 'centerRight':
    case 'bottomRight':
      horizontalAlignment = 'right';
      break;
  }

  return [verticalAlignment, horizontalAlignment];
};

const alignMeasurePolicy =
  (options: AlignProps): Measure =>
  (measurables, constraints: Constraints) => {
    console.log('entering alignment node');
    const [mov, fix] = measurables.map((measurable) => measurable.measure(constraints));

    if ('left' in options) {
      console.log('aligning left', mov, fix);
    }
    if ('right' in options) {
      console.log('aligning right', mov, fix);
    }

    console.log(
      'aligning: before',
      options,
      {
        top: mov.top,
        left: mov.left,
        bottom: mov.bottom,
        right: mov.right,
        width: mov.width,
        height: mov.height,
      },
      {
        top: fix.top,
        left: fix.left,
        bottom: fix.bottom,
        right: fix.right,
        width: fix.width,
        height: fix.height,
      },
    );

    if (fix.left === undefined) fix.left = 0;
    if (fix.top === undefined) fix.top = 0;

    console.log(
      'aligning: after fix placement',
      measurables.map((m) => m.name).join(' '),
      options,
      {
        top: mov.top,
        left: mov.left,
        bottom: mov.bottom,
        right: mov.right,
        width: mov.width,
        height: mov.height,
      },
      {
        top: fix.top,
        left: fix.left,
        bottom: fix.bottom,
        right: fix.right,
        width: fix.width,
        height: fix.height,
      },
    );

    let verticalAlignment: 'top' | 'center' | 'bottom' | undefined;
    let horizontalAlignment: 'left' | 'center' | 'right' | undefined;
    if ('topLeft' in options) {
      verticalAlignment = 'top';
      horizontalAlignment = 'left';
    } else if ('topCenter' in options) {
      verticalAlignment = 'top';
      horizontalAlignment = 'center';
    } else if ('topRight' in options) {
      verticalAlignment = 'top';
      horizontalAlignment = 'right';
    } else if ('centerLeft' in options) {
      verticalAlignment = 'center';
      horizontalAlignment = 'left';
    } else if ('center' in options) {
      verticalAlignment = 'center';
      horizontalAlignment = 'center';
    } else if ('centerRight' in options) {
      verticalAlignment = 'center';
      horizontalAlignment = 'right';
    } else if ('bottomLeft' in options) {
      verticalAlignment = 'bottom';
      horizontalAlignment = 'left';
    } else if ('bottomCenter' in options) {
      verticalAlignment = 'bottom';
      horizontalAlignment = 'center';
    } else if ('bottomRight' in options) {
      verticalAlignment = 'bottom';
      horizontalAlignment = 'right';
    } else if ('top' in options) {
      verticalAlignment = 'top';
    } else if ('centerVertically' in options) {
      verticalAlignment = 'center';
    } else if ('bottom' in options) {
      verticalAlignment = 'bottom';
    } else if ('left' in options) {
      horizontalAlignment = 'left';
    } else if ('centerHorizontally' in options) {
      horizontalAlignment = 'center';
    } else if ('right' in options) {
      horizontalAlignment = 'right';
    } else {
      throw new Error('invalid alignment options');
    }

    let toVerticalAlignment: 'top' | 'center' | 'bottom' | undefined;
    let toHorizontalAlignment: 'left' | 'center' | 'right' | undefined;
    if ('to' in options) {
      if (options.to !== undefined) [toVerticalAlignment, toHorizontalAlignment] = splitAlignment(options.to);
    } else {
      toVerticalAlignment = verticalAlignment;
      toHorizontalAlignment = horizontalAlignment;
    }

    console.log('alignment', verticalAlignment, horizontalAlignment);
    console.log('toAlignment', toVerticalAlignment, toHorizontalAlignment);

    let fixAnchor: { x?: number; y?: number } = {};
    if (toVerticalAlignment === 'top') {
      fixAnchor.y = fix.top ?? 0;
    } else if (toVerticalAlignment === 'center') {
      if (fix.height === undefined) {
        throw new Error('cannot center align vertically without height');
      }
      console.log('fix.height', fix.height);
      fixAnchor.y = (fix.top ?? 0) + fix.height / 2;
      console.log('fixAnchor', fixAnchor, (fix.top ?? 0) + fix.height / 2);
    } else if (toVerticalAlignment === 'bottom') {
      fixAnchor.y = fix.bottom ?? 0;
    }
    if (toHorizontalAlignment === 'left') {
      fixAnchor.x = fix.left ?? 0;
    } else if (toHorizontalAlignment === 'center') {
      if (fix.width === undefined) {
        throw new Error('cannot center align horizontally without width');
      }
      // console.log('fixwidth', fix.width);
      // console.log('fixwidth/2', fix.width / 2);
      fixAnchor.x = (fix.left ?? 0) + fix.width / 2;
    } else if (toHorizontalAlignment === 'right') {
      console.log('fixins', {
        fixRight: fix.right,
        fixLeft: fix.left,
        fixWidth: fix.width,
      });
      fixAnchor.x = fix.right ?? 0;
      console.log('fixAnchor: right', fixAnchor);
    }

    if (horizontalAlignment !== undefined) {
      if (fixAnchor.x === undefined) {
        debugger;
        throw new Error('cannot align horizontally. fixAnchor.x is undefined');
      }
      switch (horizontalAlignment) {
        case 'left':
          // console.log(
          //   'left',
          //   fixAnchor.x,
          //   options,
          //   {
          //     left: mov.left,
          //     top: mov.top,
          //     right: mov.right,
          //     bottom: mov.bottom,
          //     width: mov.width,
          //     height: mov.height,
          //   },
          //   {
          //     left: fix.left,
          //     top: fix.top,
          //     right: fix.right,
          //     bottom: fix.bottom,
          //     width: fix.width,
          //     height: fix.height,
          //   },
          // );
          mov.left = fixAnchor.x;
          // console.log(
          //   'left-after',
          //   mov.left,
          //   options,
          //   {
          //     left: mov.left,
          //     top: mov.top,
          //     right: mov.right,
          //     bottom: mov.bottom,
          //     width: mov.width,
          //     height: mov.height,
          //   },
          //   {
          //     left: fix.left,
          //     top: fix.top,
          //     right: fix.right,
          //     bottom: fix.bottom,
          //     width: fix.width,
          //     height: fix.height,
          //   },
          // );
          break;
        case 'center':
          if (mov.width !== undefined) {
            console.log('fix anchor', fixAnchor);
            console.log('horizontal center', mov, mov.left, mov.width, fixAnchor.x - mov.width / 2);
            mov.left = fixAnchor.x - mov.width / 2;
            console.log('left set to', mov.left, 'in horizontal center');
            console.log('horizontal center', mov, mov.left, mov.width);
          } else {
            throw new Error('cannot align horizontally');
          }
          break;
        case 'right':
          mov.right = fixAnchor.x;
          break;
      }
    }

    if (verticalAlignment !== undefined) {
      if (fixAnchor.y === undefined) {
        throw new Error('cannot align vertically. fixAnchor.y is undefined');
      }
      switch (verticalAlignment) {
        case 'top':
          mov.top = fixAnchor.y;
          break;
        case 'center':
          if (mov.height !== undefined) {
            mov.top = fixAnchor.y - mov.height / 2;
          }
          break;
        case 'bottom':
          mov.bottom = fixAnchor.y;
          break;
      }
    }

    console.log(
      'aligning: after',
      options,
      {
        left: mov.left,
        top: mov.top,
        right: mov.right,
        bottom: mov.bottom,
        width: mov.width,
        height: mov.height,
      },
      {
        left: fix.left,
        top: fix.top,
        right: fix.right,
        bottom: fix.bottom,
        width: fix.width,
        height: fix.height,
      },
    );

    const left = Math.min(mov.left ?? -Infinity, fix.left ?? -Infinity);
    const top = Math.min(mov.top ?? -Infinity, fix.top ?? -Infinity);
    const right = Math.max(mov.right ?? Infinity, fix.right ?? Infinity);
    const bottom = Math.max(mov.bottom ?? Infinity, fix.bottom ?? Infinity);

    let width;
    if (left === -Infinity || right === Infinity) {
      width = undefined;
    } else {
      width = right - left;
    }

    let height;
    if (top === -Infinity || bottom === Infinity) {
      height = undefined;
    } else {
      height = bottom - top;
    }

    console.log('aligning: result', {
      left,
      top,
      right,
      bottom,
      width,
      height,
    });

    return {
      left: left === -Infinity ? undefined : left,
      top: top === -Infinity ? undefined : top,
      right: right === Infinity ? undefined : right,
      bottom: bottom === Infinity ? undefined : bottom,
      width,
      height,
    };
  };

export const Align = LayoutFn((props: AlignProps) => alignMeasurePolicy(props));
Align.displayName = 'Align';
