///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
//   This application incorporates Open Design Alliance software pursuant to a
//   license agreement with Open Design Alliance.
//   Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////
import { Point2d } from "../Common/Geometry";

export function createHtmlElementIfNeed(
  element: HTMLElement,
  targetElement: HTMLElement,
  dataTestId: string
): HTMLElement {
  if (!element) {
    element = document.createElement("div");
    element.setAttribute("data-testid", dataTestId);

    targetElement.appendChild(element);
  }
  return element;
}

export function destroyHtmlElement(element: HTMLElement, targetElement: HTMLElement) {
  if (element) {
    targetElement.removeChild(element);
  }
  return null;
}

export function worldToScreen(gePoint: number[], moduleInstance, viewer): Point2d {
  const worldPoint = moduleInstance.Point3d.createFromArray(gePoint);
  const avp = viewer.activeView;
  const mtx = avp.worldToDeviceMatrix;

  const devicePoint = worldPoint.transformBy(mtx);

  const res = { x: devicePoint.x / window.devicePixelRatio, y: devicePoint.y / window.devicePixelRatio };

  mtx.delete();
  worldPoint.delete();
  devicePoint.delete();
  avp.delete();
  return res;
}

export function getDistance(gePoint1: number[], gePoint2: number[], moduleInstance: any) {
  const tvPoint1 = moduleInstance.Point3d.createFromArray(gePoint1);
  const tvPoint2 = moduleInstance.Point3d.createFromArray(gePoint2);

  const distance = tvPoint1.distanceTo(tvPoint2).toFixed(2);

  tvPoint1.delete();
  tvPoint2.delete();

  return distance;
}

export function getAngle(geStart: number[], geOrigin: number[], geEnd: number[], moduleInstance: any): number {
  const tvStart = moduleInstance.Point3d.createFromArray(geStart);
  const tvOrigin = moduleInstance.Point3d.createFromArray(geOrigin);
  const tvEnd = moduleInstance.Point3d.createFromArray(geEnd);

  const s1 = tvStart.sub(tvOrigin);
  const s2 = tvEnd.sub(tvOrigin);

  const v1 = s1.asVector();
  const v2 = s2.asVector();

  const angle = (180 * v1.angleTo(v2)) / Math.PI;

  return angle;
}

export function getDataForDrawLine(p1: Point2d, p2: Point2d) {
  const dx = p2.x - p1.x;
  const dy = p2.y - p1.y;
  let angle = (180 * Math.atan(dy / dx)) / Math.PI;
  if (dx < 0) {
    angle -= 180;
  }
  const width = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
  return { angle, width };
}

function normalizeFloat(value: number): number {
  return value < 0 ? Math.ceil(value) : Math.floor(value);
}

const lineSegmentsIntersect = (p1: Point2d, p2: Point2d, p3: Point2d, p4: Point2d): false | Point2d => {
  const a_dx = p2.x - p1.x;
  const a_dy = p2.y - p1.y;
  const b_dx = p4.x - p3.x;
  const b_dy = p4.y - p3.y;
  const s = (-a_dy * (p1.x - p3.x) + a_dx * (p1.y - p3.y)) / (-b_dx * a_dy + a_dx * b_dy);
  const t = (+b_dx * (p1.y - p3.y) - b_dy * (p1.x - p3.x)) / (-b_dx * a_dy + a_dx * b_dy);
  return s >= 0 && s <= 1 && t >= 0 && t <= 1
    ? {
        x: normalizeFloat(p1.x + t * a_dx),
        y: normalizeFloat(p1.y + t * a_dy),
      }
    : false;
};

function checkSegmentsIntersect(p1: Point2d, p2: Point2d, p3: Point2d, p4: Point2d, res: Point2d[]): void {
  const r = lineSegmentsIntersect(p1, p2, p3, p4);
  if (r) {
    res.push(r);
  }
}

export function isInsideRect(p: Point2d, width: number, height: number): boolean {
  return p.x <= width && p.x >= 0 && p.y <= height && p.y >= 0;
}

export function getDataForDrawLineWithFixed(p1: Point2d, p2: Point2d, width: number, height: number) {
  const pLU = { x: 0, y: 0 };
  const pRU = { x: width, y: 0 };

  const pLB = { x: 0, y: height };
  const pRB = { x: width, y: height };

  const intersects: Point2d[] = [];

  checkSegmentsIntersect(p1, p2, pLU, pRU, intersects);
  checkSegmentsIntersect(p1, p2, pLU, pLB, intersects);
  checkSegmentsIntersect(p1, p2, pLB, pRB, intersects);
  checkSegmentsIntersect(p1, p2, pRB, pRU, intersects);

  let fixedP1: Point2d = null;
  let fixedP2: Point2d = null;

  if (intersects.length === 0) {
    fixedP1 = p1;
    fixedP2 = p2;
  } else if (intersects.length === 1) {
    if (isInsideRect(p1, width, height)) {
      fixedP1 = p1;
      fixedP2 = intersects[0];
    } else {
      fixedP1 = intersects[0];
      fixedP2 = p2;
    }
  } else {
    fixedP1 = intersects[0];
    fixedP2 = intersects[1];
  }

  const dx = fixedP2.x - fixedP1.x;
  const dy = fixedP2.y - fixedP1.y;
  let angle = (180 * Math.atan(dy / dx)) / Math.PI;
  if (dx < 0) {
    angle -= 180;
  }
  const size = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
  return { angle, width: size, p1: fixedP1, p2: fixedP2 };
}

export function onSetCallback(element: HTMLElement, cb: () => void): void {
  if (element) {
    element.onclick = cb ? () => cb() : () => {};
  }
}

export function onSetSelectivity(element: HTMLElement, enable: boolean): void {
  element.style.pointerEvents = enable ? "auto" : "none";
}
