/**
 * @file MatchProperties.tsx
 * @description Component that provides functionality to match visual properties between shapes
 * in PowerPoint presentations. This allows users to copy line, fill, and text properties from
 * one shape to multiple other shapes, ensuring consistent styling across elements.
 */

import * as React from "react";
import { ColorRegular } from "@fluentui/react-icons";
import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles";
import ActionButton from "./ActionButton";
import { getErrorMessage } from "../types/office-types";

// Property types that can be matched
enum PropertyType {
  Line = "line",
  Fill = "fill",
  Text = "text"
}

// Shape property loading configuration
const SHAPE_PROPERTIES = {
  line: [
    "lineFormat/weight",
    "lineFormat/dashStyle",
    "lineFormat/color"
  ],
  fill: [
    "fill/transparency",
    "fill/foregroundColor"
  ],
  text: [
    "textFrame"
  ]
};

// Font property loading configuration
const FONT_PROPERTIES = [
  "font/name",
  "font/size",
  "font/bold",
  "font/italic",
  "font/underline",
  "font/color"
];

export const MatchProperties: React.FC = () => {
  const styles = useCommonStyles();
  const { setStatusMessage, setStatusType } = useStatusContext();

  /**
   * Validates that multiple shapes are selected
   * @param shapes The collection of selected shapes
   * @returns True if validation passes, false otherwise
   */
  const validateShapeSelection = (shapes: PowerPoint.ShapeScopedCollection): boolean => {
    if (shapes.items.length === 0) {
      setStatusMessage("No shapes are selected. Please select shapes first.");
      setStatusType("warning");
      return false;
    }

    if (shapes.items.length === 1) {
      setStatusMessage("Please select multiple shapes to copy properties.");
      setStatusType("warning");
      return false;
    }

    return true;
  };

  /**
   * Loads all necessary properties from the source shape
   * @param sourceShape The shape to copy properties from
   * @param context The PowerPoint request context
   * @returns Object containing loaded property information
   */
  const loadSourceProperties = async (
    sourceShape: PowerPoint.Shape,
    context: PowerPoint.RequestContext
  ): Promise<{ hasText: boolean }> => {
    // Load basic shape properties
    const propertiesToLoad = [
      ...SHAPE_PROPERTIES.line,
      ...SHAPE_PROPERTIES.fill,
      ...SHAPE_PROPERTIES.text
    ];
    
    sourceShape.load(propertiesToLoad);
    await context.sync();
    
    // Check if the shape has text and load text properties if it does
    let hasText = false;
    if (sourceShape.textFrame) {
      sourceShape.textFrame.load("textRange");
      await context.sync();
      
      if (sourceShape.textFrame.textRange) {
        sourceShape.textFrame.textRange.load(FONT_PROPERTIES);
        await context.sync();
        hasText = true;
      }
    }

    return { hasText };
  };

  /**
   * Preloads text properties for all target shapes in batches
   * @param shapes The collection of shapes
   * @param hasText Whether the source shape has text
   * @param context The PowerPoint request context
   */
  const preloadTargetTextProperties = async (
    shapes: PowerPoint.ShapeScopedCollection,
    hasText: boolean,
    context: PowerPoint.RequestContext
  ): Promise<void> => {
    if (!hasText) return;
    
    // Batch 1: Load textFrame for all target shapes
    for (let i = 1; i < shapes.items.length; i++) {
      shapes.items[i].load("textFrame");
    }
    await context.sync();
    
    // Batch 2: For shapes that have textFrames, load their textRanges
    for (let i = 1; i < shapes.items.length; i++) {
      if (shapes.items[i].textFrame) {
        shapes.items[i].textFrame.load("textRange");
      }
    }
    await context.sync();
  };

  /**
   * Applies line properties from source to target shape
   * @param sourceShape The shape to copy properties from
   * @param targetShape The shape to apply properties to
   * @returns True if any properties were applied
   */
  const applyLineProperties = (
    sourceShape: PowerPoint.Shape,
    targetShape: PowerPoint.Shape
  ): boolean => {
    let propertiesApplied = false;
    
    try {
      // Line weight
      if (sourceShape.lineFormat.weight !== undefined) {
        targetShape.lineFormat.weight = sourceShape.lineFormat.weight;
        propertiesApplied = true;
      }
      
      // Line style
      if (sourceShape.lineFormat.dashStyle !== undefined) {
        targetShape.lineFormat.dashStyle = sourceShape.lineFormat.dashStyle;
        propertiesApplied = true;
      }
      
      // Line color
      if (sourceShape.lineFormat.color !== undefined) {
        targetShape.lineFormat.color = sourceShape.lineFormat.color;
        propertiesApplied = true;
      }
    } catch (err) {
      console.error("Error applying line format:", getErrorMessage(err));
    }
    
    return propertiesApplied;
  };

  /**
   * Applies fill properties from source to target shape
   * @param sourceShape The shape to copy properties from
   * @param targetShape The shape to apply properties to
   * @returns True if any properties were applied
   */
  const applyFillProperties = (
    sourceShape: PowerPoint.Shape,
    targetShape: PowerPoint.Shape
  ): boolean => {
    let propertiesApplied = false;
    
    try {
      // Fill transparency
      if (sourceShape.fill.transparency !== undefined) {
        targetShape.fill.transparency = sourceShape.fill.transparency;
        propertiesApplied = true;
      }
      
      // Fill color
      if (sourceShape.fill.foregroundColor !== undefined) {
        targetShape.fill.foregroundColor = sourceShape.fill.foregroundColor;
        propertiesApplied = true;
      }
    } catch (err) {
      console.error("Error applying fill format:", getErrorMessage(err));
    }
    
    return propertiesApplied;
  };


  /**
   * Applies text properties from source to target shape
   * @param sourceShape The shape to copy properties from
   * @param targetShape The shape to apply properties to
   * @returns True if text properties were applied
   */
  const applyTextProperties = (
    sourceShape: PowerPoint.Shape,
    targetShape: PowerPoint.Shape
  ): boolean => {
    try {
      if (sourceShape.textFrame?.textRange && targetShape.textFrame?.textRange) {
        const sourceFont = sourceShape.textFrame.textRange.font;
        const targetFont = targetShape.textFrame.textRange.font;
        
        // Apply font properties in batch
        if (sourceFont.name !== undefined) {
          targetFont.name = sourceFont.name;
        }
        
        if (sourceFont.size !== undefined) {
          targetFont.size = sourceFont.size;
        }
        
        if (sourceFont.bold !== undefined) {
          targetFont.bold = sourceFont.bold;
        }
        
        if (sourceFont.italic !== undefined) {
          targetFont.italic = sourceFont.italic;
        }
        
        if (sourceFont.underline !== undefined) {
          targetFont.underline = sourceFont.underline;
        }
        
        if (sourceFont.color !== undefined) {
          targetFont.color = sourceFont.color;
        }
        
        return true;
      }
    } catch (err) {
      console.error("Error applying text format:", getErrorMessage(err));
    }
    
    return false;
  };

  /**
   * Main function to match properties from the first selected shape to others
   */
  const matchPropertiesToFirstSelected = async (): Promise<void> => {
    await PowerPoint.run(async (context) => {
      // Get the selected shapes
      const shapes = context.presentation.getSelectedShapes();
      shapes.load("items");
      await context.sync();

      // Validate shape selection
      if (!validateShapeSelection(shapes)) {
        return;
      }

      // Get the first shape to use as template
      const sourceShape = shapes.items[0];
      
      // Load all necessary properties from the source shape
      const { hasText } = await loadSourceProperties(sourceShape, context);
      
      // Preload text properties for target shapes if needed
      await preloadTargetTextProperties(shapes, hasText, context);
      
      // Apply properties to all target shapes
      let successCount = 0;
      let textStyleCount = 0;
      
      for (let i = 1; i < shapes.items.length; i++) {
        const targetShape = shapes.items[i];
        let shapeUpdated = false;
        
        // Apply different property types
        const lineApplied = applyLineProperties(sourceShape, targetShape);
        const fillApplied = applyFillProperties(sourceShape, targetShape);
        
        // Apply text properties if available
        if (hasText) {
          const textApplied = applyTextProperties(sourceShape, targetShape);
          if (textApplied) {
            textStyleCount++;
          }
        }
        
        // Count successful shape updates
        if (lineApplied || fillApplied) {
          shapeUpdated = true;
        }
        
        if (shapeUpdated) {
          successCount++;
        }
      }
      
      // Single sync after all property changes
      await context.sync();

      // Generate appropriate status message
      if (successCount > 0) {
        const propertyTypes = [];
        
        // Build a detailed status message
        if (sourceShape.lineFormat) propertyTypes.push("line");
        if (sourceShape.fill) propertyTypes.push("fill");
        
        let message = `Applied ${propertyTypes.join(", ")} properties to ${successCount} shapes`;
        if (textStyleCount > 0) {
          message += ` (including text styling on ${textStyleCount})`;
        }
        
        setStatusMessage(message);
        setStatusType("success");
      } else {
        setStatusMessage("Couldn't apply properties. Try selecting different shapes.");
        setStatusType("error");
      }
    });
  };

  return (
    <div className={styles.container}>
      <div className={styles.buttonGroup}>
        <ActionButton
          title="Match Properties"
          icon={<ColorRegular />}
          onClick={matchPropertiesToFirstSelected}
        />
      </div>
    </div>
  );
};

export default MatchProperties;
