package io.synor.compiler;

import java.util.*;

/**
 * Synor Compiler SDK Types
 */

// Enumerations

enum OptimizationLevel {
    NONE("none"),
    BASIC("basic"),
    SIZE("size"),
    AGGRESSIVE("aggressive");

    private final String value;

    OptimizationLevel(String value) {
        this.value = value;
    }

    public String toApiString() {
        return value;
    }

    public static OptimizationLevel fromApiString(String value) {
        for (OptimizationLevel level : values()) {
            if (level.value.equals(value)) {
                return level;
            }
        }
        return SIZE;
    }
}

// Configuration

class StripOptions {
    private boolean stripDebug = true;
    private boolean stripProducers = true;
    private boolean stripNames = true;
    private boolean stripCustom = true;
    private List<String> preserveSections = new ArrayList<>();
    private boolean stripUnused = true;

    public boolean isStripDebug() { return stripDebug; }
    public void setStripDebug(boolean stripDebug) { this.stripDebug = stripDebug; }

    public boolean isStripProducers() { return stripProducers; }
    public void setStripProducers(boolean stripProducers) { this.stripProducers = stripProducers; }

    public boolean isStripNames() { return stripNames; }
    public void setStripNames(boolean stripNames) { this.stripNames = stripNames; }

    public boolean isStripCustom() { return stripCustom; }
    public void setStripCustom(boolean stripCustom) { this.stripCustom = stripCustom; }

    public List<String> getPreserveSections() { return preserveSections; }
    public void setPreserveSections(List<String> preserveSections) { this.preserveSections = preserveSections; }

    public boolean isStripUnused() { return stripUnused; }
    public void setStripUnused(boolean stripUnused) { this.stripUnused = stripUnused; }

    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("strip_debug", stripDebug);
        map.put("strip_producers", stripProducers);
        map.put("strip_names", stripNames);
        map.put("strip_custom", stripCustom);
        map.put("preserve_sections", preserveSections);
        map.put("strip_unused", stripUnused);
        return map;
    }
}

class CompilerConfig {
    private final String apiKey;
    private String endpoint = "https://compiler.synor.io/v1";
    private long timeout = 60000;
    private int retries = 3;
    private boolean debug = false;
    private OptimizationLevel optimizationLevel = OptimizationLevel.SIZE;
    private StripOptions stripOptions;
    private int maxContractSize = 256 * 1024;
    private boolean useWasmOpt = true;
    private boolean validate = true;
    private boolean extractMetadata = true;
    private boolean generateAbi = true;

    public CompilerConfig(String apiKey) {
        this.apiKey = apiKey;
    }

    public String getApiKey() { return apiKey; }
    public String getEndpoint() { return endpoint; }
    public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
    public long getTimeout() { return timeout; }
    public void setTimeout(long timeout) { this.timeout = timeout; }
    public int getRetries() { return retries; }
    public void setRetries(int retries) { this.retries = retries; }
    public boolean isDebug() { return debug; }
    public void setDebug(boolean debug) { this.debug = debug; }
    public OptimizationLevel getOptimizationLevel() { return optimizationLevel; }
    public void setOptimizationLevel(OptimizationLevel level) { this.optimizationLevel = level; }
    public StripOptions getStripOptions() { return stripOptions; }
    public void setStripOptions(StripOptions stripOptions) { this.stripOptions = stripOptions; }
    public int getMaxContractSize() { return maxContractSize; }
    public void setMaxContractSize(int maxContractSize) { this.maxContractSize = maxContractSize; }
    public boolean isUseWasmOpt() { return useWasmOpt; }
    public void setUseWasmOpt(boolean useWasmOpt) { this.useWasmOpt = useWasmOpt; }
    public boolean isValidate() { return validate; }
    public void setValidate(boolean validate) { this.validate = validate; }
    public boolean isExtractMetadata() { return extractMetadata; }
    public void setExtractMetadata(boolean extractMetadata) { this.extractMetadata = extractMetadata; }
    public boolean isGenerateAbi() { return generateAbi; }
    public void setGenerateAbi(boolean generateAbi) { this.generateAbi = generateAbi; }
}

class CompilationRequest {
    private OptimizationLevel optimizationLevel;
    private StripOptions stripOptions;
    private Boolean useWasmOpt;
    private Boolean validate;
    private Boolean extractMetadata;
    private Boolean generateAbi;

    public OptimizationLevel getOptimizationLevel() { return optimizationLevel; }
    public void setOptimizationLevel(OptimizationLevel optimizationLevel) { this.optimizationLevel = optimizationLevel; }
    public StripOptions getStripOptions() { return stripOptions; }
    public void setStripOptions(StripOptions stripOptions) { this.stripOptions = stripOptions; }
    public Boolean getUseWasmOpt() { return useWasmOpt; }
    public void setUseWasmOpt(Boolean useWasmOpt) { this.useWasmOpt = useWasmOpt; }
    public Boolean getValidate() { return validate; }
    public void setValidate(Boolean validate) { this.validate = validate; }
    public Boolean getExtractMetadata() { return extractMetadata; }
    public void setExtractMetadata(Boolean extractMetadata) { this.extractMetadata = extractMetadata; }
    public Boolean getGenerateAbi() { return generateAbi; }
    public void setGenerateAbi(Boolean generateAbi) { this.generateAbi = generateAbi; }
}

// Validation

class ValidationError {
    private String code;
    private String message;
    private String location;

    public String getCode() { return code; }
    public String getMessage() { return message; }
    public String getLocation() { return location; }
}

class ValidationResult {
    private boolean valid;
    private List<ValidationError> errors = new ArrayList<>();
    private List<String> warnings = new ArrayList<>();
    private int exportCount;
    private int importCount;
    private int functionCount;
    private int[] memoryPages = {0, 0};

    public boolean isValid() { return valid; }
    public List<ValidationError> getErrors() { return errors; }
    public List<String> getWarnings() { return warnings; }
    public int getExportCount() { return exportCount; }
    public int getImportCount() { return importCount; }
    public int getFunctionCount() { return functionCount; }
    public int[] getMemoryPages() { return memoryPages; }
}

// Metadata

class ContractMetadata {
    private String name;
    private String version;
    private List<String> authors = new ArrayList<>();
    private String description;
    private String license;
    private String repository;
    private Long buildTimestamp;
    private String rustVersion;
    private String sdkVersion;
    private Map<String, String> custom = new HashMap<>();

    public String getName() { return name; }
    public String getVersion() { return version; }
    public List<String> getAuthors() { return authors; }
    public String getDescription() { return description; }
    public String getLicense() { return license; }
    public String getRepository() { return repository; }
    public Long getBuildTimestamp() { return buildTimestamp; }
    public String getRustVersion() { return rustVersion; }
    public String getSdkVersion() { return sdkVersion; }
    public Map<String, String> getCustom() { return custom; }
}

// ABI Types

class TypeInfo {
    private String kind;
    private Integer size;
    private TypeInfo element;
    private TypeInfo inner;
    private List<TypeInfo> elements;
    private String name;
    private List<TypeField> fields;

    public String getKind() { return kind; }
    public Integer getSize() { return size; }
    public TypeInfo getElement() { return element; }
    public TypeInfo getInner() { return inner; }
    public List<TypeInfo> getElements() { return elements; }
    public String getName() { return name; }
    public List<TypeField> getFields() { return fields; }

    public String getTypeName() {
        switch (kind) {
            case "u8": case "u16": case "u32": case "u64": case "u128":
            case "i8": case "i16": case "i32": case "i64": case "i128":
            case "bool": case "string": case "bytes": case "address": case "hash256":
                return kind;
            case "fixedBytes":
                return "bytes" + (size != null ? size : 0);
            case "array":
                return (element != null ? element.getTypeName() : "") + "[]";
            case "fixedArray":
                return (element != null ? element.getTypeName() : "") + "[" + (size != null ? size : 0) + "]";
            case "option":
                return (inner != null ? inner.getTypeName() : "") + "?";
            case "tuple":
                if (elements != null) {
                    StringBuilder sb = new StringBuilder("(");
                    for (int i = 0; i < elements.size(); i++) {
                        if (i > 0) sb.append(", ");
                        sb.append(elements.get(i).getTypeName());
                    }
                    sb.append(")");
                    return sb.toString();
                }
                return "()";
            case "struct":
                return name != null ? name : "struct";
            default:
                return name != null ? name : "unknown";
        }
    }
}

class TypeField {
    private String name;
    private TypeInfo type;

    public String getName() { return name; }
    public TypeInfo getType() { return type; }
}

class ParamAbi {
    private String name;
    private TypeInfo type;
    private boolean indexed;

    public String getName() { return name; }
    public TypeInfo getType() { return type; }
    public boolean isIndexed() { return indexed; }
}

class FunctionAbi {
    private String name;
    private String selector;
    private List<ParamAbi> inputs = new ArrayList<>();
    private List<ParamAbi> outputs = new ArrayList<>();
    private boolean view;
    private boolean payable;
    private String doc;

    public String getName() { return name; }
    public String getSelector() { return selector; }
    public List<ParamAbi> getInputs() { return inputs; }
    public List<ParamAbi> getOutputs() { return outputs; }
    public boolean isView() { return view; }
    public boolean isPayable() { return payable; }
    public String getDoc() { return doc; }
}

class EventAbi {
    private String name;
    private String topic;
    private List<ParamAbi> params = new ArrayList<>();
    private String doc;

    public String getName() { return name; }
    public String getTopic() { return topic; }
    public List<ParamAbi> getParams() { return params; }
    public String getDoc() { return doc; }
}

class ErrorAbi {
    private String name;
    private String selector;
    private List<ParamAbi> params = new ArrayList<>();
    private String doc;

    public String getName() { return name; }
    public String getSelector() { return selector; }
    public List<ParamAbi> getParams() { return params; }
    public String getDoc() { return doc; }
}

class ContractAbi {
    private String name;
    private String version;
    private List<FunctionAbi> functions = new ArrayList<>();
    private List<EventAbi> events = new ArrayList<>();
    private List<ErrorAbi> errors = new ArrayList<>();

    public String getName() { return name; }
    public String getVersion() { return version; }
    public List<FunctionAbi> getFunctions() { return functions; }
    public List<EventAbi> getEvents() { return events; }
    public List<ErrorAbi> getErrors() { return errors; }

    public FunctionAbi findFunction(String name) {
        return functions.stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
    }

    public FunctionAbi findBySelector(String selector) {
        return functions.stream().filter(f -> f.getSelector().equals(selector)).findFirst().orElse(null);
    }
}

// Compilation Result

class CompilationResult {
    private String contractId;
    private String code;
    private String codeHash;
    private int originalSize;
    private int optimizedSize;
    private double sizeReduction;
    private ContractMetadata metadata;
    private ContractAbi abi;
    private long estimatedDeployGas;
    private ValidationResult validation;
    private List<String> warnings = new ArrayList<>();

    public String getContractId() { return contractId; }
    public String getCode() { return code; }
    public String getCodeHash() { return codeHash; }
    public int getOriginalSize() { return originalSize; }
    public int getOptimizedSize() { return optimizedSize; }
    public double getSizeReduction() { return sizeReduction; }
    public ContractMetadata getMetadata() { return metadata; }
    public ContractAbi getAbi() { return abi; }
    public long getEstimatedDeployGas() { return estimatedDeployGas; }
    public ValidationResult getValidation() { return validation; }
    public List<String> getWarnings() { return warnings; }

    public String getSizeStats() {
        return String.format("Original: %d bytes, Optimized: %d bytes, Reduction: %.1f%%",
            originalSize, optimizedSize, sizeReduction);
    }

    public byte[] decodeCode() {
        return Base64.getDecoder().decode(code);
    }
}

// Analysis

class SizeBreakdown {
    private int code;
    private int data;
    private int types;
    private int functions;
    private int memory;
    private int table;
    private int exports;
    private int imports;
    private int custom;
    private int total;

    public int getCode() { return code; }
    public int getData() { return data; }
    public int getTypes() { return types; }
    public int getFunctions() { return functions; }
    public int getMemory() { return memory; }
    public int getTable() { return table; }
    public int getExports() { return exports; }
    public int getImports() { return imports; }
    public int getCustom() { return custom; }
    public int getTotal() { return total; }
}

class FunctionAnalysis {
    private String name;
    private int size;
    private int instructionCount;
    private int localCount;
    private boolean exported;
    private long estimatedGas;

    public String getName() { return name; }
    public int getSize() { return size; }
    public int getInstructionCount() { return instructionCount; }
    public int getLocalCount() { return localCount; }
    public boolean isExported() { return exported; }
    public long getEstimatedGas() { return estimatedGas; }
}

class ImportAnalysis {
    private String module;
    private String name;
    private String kind;
    private String signature;

    public String getModule() { return module; }
    public String getName() { return name; }
    public String getKind() { return kind; }
    public String getSignature() { return signature; }
}

class SecurityIssue {
    private String severity;
    private String type;
    private String description;
    private String location;

    public String getSeverity() { return severity; }
    public String getType() { return type; }
    public String getDescription() { return description; }
    public String getLocation() { return location; }
}

class SecurityAnalysis {
    private int score;
    private List<SecurityIssue> issues = new ArrayList<>();
    private List<String> recommendations = new ArrayList<>();

    public int getScore() { return score; }
    public List<SecurityIssue> getIssues() { return issues; }
    public List<String> getRecommendations() { return recommendations; }
}

class GasAnalysis {
    private long deploymentGas;
    private Map<String, Long> functionGas = new HashMap<>();
    private long memoryInitGas;
    private long dataSectionGas;

    public long getDeploymentGas() { return deploymentGas; }
    public Map<String, Long> getFunctionGas() { return functionGas; }
    public long getMemoryInitGas() { return memoryInitGas; }
    public long getDataSectionGas() { return dataSectionGas; }
}

class ContractAnalysis {
    private SizeBreakdown sizeBreakdown;
    private List<FunctionAnalysis> functions = new ArrayList<>();
    private List<ImportAnalysis> imports = new ArrayList<>();
    private SecurityAnalysis security;
    private GasAnalysis gasAnalysis;

    public SizeBreakdown getSizeBreakdown() { return sizeBreakdown; }
    public List<FunctionAnalysis> getFunctions() { return functions; }
    public List<ImportAnalysis> getImports() { return imports; }
    public SecurityAnalysis getSecurity() { return security; }
    public GasAnalysis getGasAnalysis() { return gasAnalysis; }
}

// Error

class CompilerException extends RuntimeException {
    private final String code;
    private final Integer httpStatus;

    public CompilerException(String message, String code, Integer httpStatus) {
        super(message);
        this.code = code;
        this.httpStatus = httpStatus;
    }

    public String getCode() { return code; }
    public Integer getHttpStatus() { return httpStatus; }
}

// Constants

final class CompilerConstants {
    public static final int MAX_CONTRACT_SIZE = 256 * 1024;
    public static final int MAX_MEMORY_PAGES = 16;

    private CompilerConstants() {}
}
