// This file was moved from the `server` package to here and `server` has `strictNullChecks` disabled.
// This code was originally written with the assumption that `widget.children` is never undefined, despite the type saying otherwise.
// So we are using `!` to assert that `widget.children` is never undefined.
// Note that `!` does not change the runtime behavior of the code so the code will work the same way as before.

import type { PageDSL6, WidgetProps6 } from '../../../types/index.js';
import { generatePredictableId } from '../../../utils/appDSL.js';

const v7MainSection = () => {
  // This needs to be consistent on each autosave, otherwise the changelog will indicate diffs
  const firstSectionId = '7dsm1k3e8w';
  return {
    type: 'SECTION_WIDGET',
    widgetId: firstSectionId,
    parentId: '0',
    widgetName: 'Section1',
    detachFromLayout: true,
    isLoading: false,
    isVisible: true,
    canExtend: true,
    dragDisabled: true,
    shouldScrollContents: true,
    children: [
      {
        widgetName: 'Canvas1',
        backgroundColor: 'none',
        detachFromLayout: true,
        dragDisabled: true,
        parentId: firstSectionId,
        widgetId: generatePredictableId('Canvas1'),
        isLoading: false,
        isVisible: true,
        topRow: 0,
        bottomRow: 99,
        containerStyle: 'none',
        snapColumns: 96,
        snapRows: 99,
        minHeight: 1292,
        type: 'CANVAS_WIDGET',
        canExtend: true,
        dynamicBindingPathList: [],
        children: []
      }
    ],

    // The properties below will be removed as part of Mark's migration
    // to use the new Dimension type, but they are needed for now otherwise the
    // DSL rendering breaks due to dependencies on these properties
    // even though they are unused by the widget itself
    topRow: 0,
    bottomRow: 99,
    snapColumns: 96,
    snapRows: 99,
    leftColumn: 0,
    rightColumn: 96
  } as WidgetProps6;
};

const generateSectionsLayout = (): PageDSL6 => {
  return {
    version: 6,
    type: 'PAGE_WIDGET',
    // PAGE_WIDGET is always id '0'. It's the only one that exists and cannot be deleted
    widgetId: '0',
    widgetName: 'Page',
    backgroundColor: 'none', // is this needed? Seems worth keeping for now
    detachFromLayout: true,
    children: [v7MainSection()],
    apis: { apiMap: {} },
    stateVars: { stateVarMap: {} },
    timers: { timerMap: {} },
    canExtend: true,
    dynamicBindingPathList: [],
    // The properties below will be removed as part of Mark's migration
    // to use the new Dimension type, but they are needed for now otherwise the
    // DSL rendering breaks due to dependencies on these properties
    // even though they are unused by the widget itself
    topRow: 0,
    bottomRow: 99,
    snapColumns: 96,
    snapRows: 99
  };
};

const createPage = (): PageDSL6 => generateSectionsLayout();

export const findAllInstancesOfWidgetNameWithPrefix = (dsl: PageDSL6, prefix: string): string[] => {
  const names: string[] = [];

  const traverseWidgets = (widget: WidgetProps6) => {
    // Check widgetName at the root of the widget.
    if (widget.widgetName && typeof widget.widgetName === 'string' && widget.widgetName.toLowerCase().startsWith(prefix)) {
      names.push(widget.widgetName);
    }

    // Only traverse the children property if it exists.
    if (Array.isArray(widget.children)) {
      for (const child of widget.children) {
        traverseWidgets(child);
      }
    }
  };

  traverseWidgets(dsl);
  return names;
};

export const findNextWidgetNameWithSuffix = (startingName: string, existingWidgetNamesWithPrefix: string[], suffix?: string): string => {
  // Numbered items start at 1, not 0
  // Suffixed items start without a suffix
  let nextWidgetName = startingName + (suffix ? '' : 1);

  if (existingWidgetNamesWithPrefix.length > 0) {
    let findNextName = true;
    let index = 1;

    while (findNextName) {
      if (!existingWidgetNamesWithPrefix.find((n) => n.toLocaleLowerCase() === nextWidgetName.toLowerCase())) {
        findNextName = false;
      } else {
        if (suffix) {
          nextWidgetName = `${startingName}${suffix.repeat(index)}`;
        } else {
          nextWidgetName = `${startingName}${index}`;
        }
        index++;
      }
    }
  }

  return nextWidgetName;
};

const v7SlideoutOrModalSection = (nextName: string, children: WidgetProps6[] | WidgetProps6[]) => {
  const id = generatePredictableId(nextName);
  return {
    type: 'SECTION_WIDGET',
    widgetId: id,
    widgetName: nextName,
    children: children.map((child) => {
      child.parentId = id;
      return child;
    }),
    detachFromLayout: true,
    isVisible: true,
    isLoading: true,
    canExtend: true,
    dragDisabled: true,
    shouldScrollContents: true,
    snapColumns: 96,
    topRow: 0,
    bottomRow: 99
  } as WidgetProps6;
};

// Insert a new "Page" widget at the top level.
// This will be the new root level widget containing all other widgets.
// Then insert a single "Section" widget into the Page children. All v7 DSL apps must have at least a single
// Section that widgets live in.
// Then move the v6 DSL CANVAS root widget ("MainCanvasWidget") into the Section.
// Canvas widgets are now just a type of widget that can live inside a Section and they are considered the Section's "columns"
export const migrateToSections = (page: PageDSL6): PageDSL6 => {
  const { apis, stateVars, timers, cachedData, ...originalMainCanvasWidget } = page;

  // Rename the widgetId to a random UUID as "0" is reserved for the page
  delete (originalMainCanvasWidget as { version?: typeof originalMainCanvasWidget.version }).version;
  originalMainCanvasWidget.widgetId = generatePredictableId(originalMainCanvasWidget.widgetName);

  originalMainCanvasWidget.widgetName = findNextWidgetNameWithSuffix('Canvas', findAllInstancesOfWidgetNameWithPrefix(page, 'canvas'));

  const dsl = createPage();
  // Even though our widget naming system is case sensitive, we want to make naming clear so we won't
  // use Page if a widget with "page" (lowercase P) exists
  dsl.widgetName = findNextWidgetNameWithSuffix('Page', findAllInstancesOfWidgetNameWithPrefix(page, 'page'), '_');

  dsl.children![0].widgetId = generatePredictableId(dsl.children![0].widgetName);

  // Move the main canvas widget into the first section
  // this will overwrite the default empty canvas that is already in the default layout
  dsl.children![0].children = [originalMainCanvasWidget];
  originalMainCanvasWidget.parentId = dsl.children![0].widgetId;

  // Update parent id of all children of the original main canvas widget
  for (const child of originalMainCanvasWidget.children!) {
    child.parentId = originalMainCanvasWidget.widgetId;
  }

  const upgradeModalsAndSlideouts = (widget: WidgetProps6) => {
    for (const child of widget.children ?? []) {
      if (child.type === 'SLIDEOUT_WIDGET' || child.type === 'MODAL_WIDGET') {
        const firstChild = child.children?.[0];
        const names = findAllInstancesOfWidgetNameWithPrefix(dsl, 'body');
        const nextName = findNextWidgetNameWithSuffix('Body', names);
        const bodySection = v7SlideoutOrModalSection(nextName, child.children!);
        bodySection.parentId = child.widgetId;

        // Copy over the position properties from the first child
        const keys: Array<keyof WidgetProps6> = ['topRow', 'bottomRow', 'leftColumn', 'rightColumn', 'snapColumns', 'snapRows'];
        keys.forEach((prop) => {
          if (firstChild?.[prop] !== undefined) bodySection[prop] = firstChild?.[prop];
        });

        child.children = [bodySection];
      }
      upgradeModalsAndSlideouts(child);
    }
  };
  upgradeModalsAndSlideouts(dsl);

  return {
    ...dsl,
    apis: { apiMap: apis?.apiMap ?? {} },
    stateVars: stateVars ?? { stateVarMap: {} },
    timers: timers ?? { timerMap: {} },
    ...(cachedData ? { cachedData } : {})
  };
};
