import sys
from pathlib import Path
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QPushButton, QLabel, QSlider, QCheckBox, QTextEdit, QFileDialog, 
                             QMessageBox, QFrame)
from PyQt6.QtGui import QPixmap, QFont
from PyQt6.QtCore import Qt
from PIL import Image, ImageOps, ImageEnhance

ASCII_CHARS = "@%#*+=-:. "

BG_PRIMARY = "#1a1a1a"
BG_SECONDARY = "#2d2d2d"
BG_TERTIARY = "#3a3a3a"
FG_TEXT = "#e0e0e0"
ACCENT_COLOR = "#00a8ff"
ACCENT_HOVER = "#0088cc"


def scale_image(image, new_width=100, max_height=None):
    width, height = image.size
    ratio = height / width / 0.55
    new_height = int(new_width * ratio)
    if max_height and new_height > max_height:
        new_height = max_height
    return image.resize((new_width, max(1, new_height)))


def map_pixels_to_ascii(image, chars=ASCII_CHARS):
    grayscale = image.convert("L")
    pixels = grayscale.getdata()
    interval = 255 / (len(chars) - 1)
    return "".join(chars[int(pixel / interval)] for pixel in pixels)


def image_to_ascii(image_path, width=100, height=None, invert=False, contrast=1.0):
    img = Image.open(image_path)
    if invert:
        img = ImageOps.invert(img.convert("RGB")).convert("RGB")
    if contrast != 1.0:
        enhancer = ImageEnhance.Contrast(img)
        img = enhancer.enhance(contrast)
    img = scale_image(img, new_width=width, max_height=height)
    ascii_str = map_pixels_to_ascii(img)
    lines = [ascii_str[i:i + img.width] for i in range(0, len(ascii_str), img.width)]
    if height and len(lines) > height:
        lines = lines[:height]
    return "\n".join(lines)


class ASCIIApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image → ASCII Art")
        self.setGeometry(100, 100, 1300, 800)
        self.image_path = None
        self.setStyleSheet(self._get_stylesheet())
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)
        main_layout.setContentsMargins(12, 12, 12, 12)
        main_layout.setSpacing(12)
        
        # Left panel
        left_panel = QFrame()
        left_panel.setFrameShape(QFrame.Shape.StyledPanel)
        left_layout = QVBoxLayout(left_panel)
        left_layout.setSpacing(8)
        
        # Title
        title = QLabel("🎨 Инструменты")
        title.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
        title.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addWidget(title)
        
        # Open button
        self.btn_open = QPushButton("📁 Открыть изображение")
        self.btn_open.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold))
        self.btn_open.setCursor(Qt.CursorShape.PointingHandCursor)
        self.btn_open.clicked.connect(self.open_image)
        left_layout.addWidget(self.btn_open)
        
        # Width control
        width_label = QLabel("Ширина (символов):")
        width_label.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        width_label.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addWidget(width_label)
        
        width_layout = QHBoxLayout()
        self.width_slider = QSlider(Qt.Orientation.Horizontal)
        self.width_slider.setMinimum(30)
        self.width_slider.setMaximum(300)
        self.width_slider.setValue(120)
        self.width_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.width_slider.setTickInterval(20)
        self.width_slider.valueChanged.connect(self._on_width_changed)
        width_layout.addWidget(self.width_slider)
        
        self.width_value = QLabel("120")
        self.width_value.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        self.width_value.setStyleSheet(f"color: {ACCENT_COLOR}; min-width: 30px;")
        width_layout.addWidget(self.width_value)
        left_layout.addLayout(width_layout)
        
        # Height control
        height_label = QLabel("Высота (строк):")
        height_label.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        height_label.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addWidget(height_label)
        
        height_layout = QHBoxLayout()
        self.height_slider = QSlider(Qt.Orientation.Horizontal)
        self.height_slider.setMinimum(10)
        self.height_slider.setMaximum(100)
        self.height_slider.setValue(50)
        self.height_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.height_slider.setTickInterval(10)
        self.height_slider.valueChanged.connect(self._on_height_changed)
        height_layout.addWidget(self.height_slider)
        
        self.height_value = QLabel("50")
        self.height_value.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        self.height_value.setStyleSheet(f"color: {ACCENT_COLOR}; min-width: 30px;")
        height_layout.addWidget(self.height_value)
        left_layout.addLayout(height_layout)
        
        # Invert checkbox
        self.invert_check = QCheckBox("🔄 Инвертировать")
        self.invert_check.setFont(QFont("Segoe UI", 10))
        self.invert_check.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addWidget(self.invert_check)
        
        # Contrast control
        contrast_label = QLabel("Контраст:")
        contrast_label.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        contrast_label.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addWidget(contrast_label)
        
        contrast_layout = QHBoxLayout()
        self.contrast_slider = QSlider(Qt.Orientation.Horizontal)
        self.contrast_slider.setMinimum(5)
        self.contrast_slider.setMaximum(20)
        self.contrast_slider.setValue(10)
        self.contrast_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.contrast_slider.setTickInterval(1)
        self.contrast_slider.valueChanged.connect(self._on_contrast_changed)
        contrast_layout.addWidget(self.contrast_slider)
        
        self.contrast_value = QLabel("1.0×")
        self.contrast_value.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
        self.contrast_value.setStyleSheet(f"color: {ACCENT_COLOR}; min-width: 30px;")
        contrast_layout.addWidget(self.contrast_value)
        left_layout.addLayout(contrast_layout)
        
        # Convert button
        self.btn_convert = QPushButton("⚡ Конвертировать")
        self.btn_convert.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold))
        self.btn_convert.setCursor(Qt.CursorShape.PointingHandCursor)
        self.btn_convert.setMinimumHeight(40)
        self.btn_convert.clicked.connect(self.convert)
        left_layout.addSpacing(20)
        left_layout.addWidget(self.btn_convert)
        
        # Action buttons
        self.btn_save = QPushButton("💾 Сохранить")
        self.btn_save.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold))
        self.btn_save.setCursor(Qt.CursorShape.PointingHandCursor)
        self.btn_save.clicked.connect(self.save_ascii)
        left_layout.addWidget(self.btn_save)
        
        self.btn_copy = QPushButton("📋 Скопировать")
        self.btn_copy.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold))
        self.btn_copy.setCursor(Qt.CursorShape.PointingHandCursor)
        self.btn_copy.clicked.connect(self.copy_to_clipboard)
        left_layout.addWidget(self.btn_copy)
        
        # Preview section
        preview_title = QLabel("📷 Предпросмотр")
        preview_title.setFont(QFont("Segoe UI", 12, QFont.Weight.Bold))
        preview_title.setStyleSheet(f"color: {FG_TEXT};")
        left_layout.addSpacing(20)
        left_layout.addWidget(preview_title)
        
        self.preview_label = QLabel()
        self.preview_label.setStyleSheet(f"background-color: {BG_TERTIARY}; border: 1px solid {BG_TERTIARY}; padding: 5px;")
        self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.preview_label.setMinimumSize(320, 240)
        self.preview_label.setMaximumSize(320, 240)
        left_layout.addWidget(self.preview_label)
        
        left_layout.addStretch()
        
        # Right panel - Output
        right_panel = QFrame()
        right_panel.setFrameShape(QFrame.Shape.StyledPanel)
        right_layout = QVBoxLayout(right_panel)
        right_layout.setSpacing(8)
        
        output_title = QLabel("✨ ASCII-арт")
        output_title.setFont(QFont("Segoe UI", 12, QFont.Weight.Bold))
        output_title.setStyleSheet(f"color: {FG_TEXT};")
        right_layout.addWidget(output_title)
        
        self.text_edit = QTextEdit()
        self.text_edit.setFont(QFont("Consolas", 7))
        self.text_edit.setReadOnly(False)
        self.text_edit.setStyleSheet(f"""
            QTextEdit {{
                background-color: {BG_SECONDARY};
                color: {ACCENT_COLOR};
                border: 1px solid {BG_TERTIARY};
                padding: 5px;
                border-radius: 4px;
            }}
            QTextEdit:focus {{
                border: 1px solid {ACCENT_COLOR};
            }}
        """)
        right_layout.addWidget(self.text_edit)
        
        main_layout.addWidget(left_panel, stretch=0)
        main_layout.addWidget(right_panel, stretch=1)
    
    def _get_stylesheet(self):
        return f"""
        QMainWindow {{
            background-color: {BG_PRIMARY};
        }}
        QFrame {{
            background-color: {BG_PRIMARY};
            border: none;
        }}
        QPushButton {{
            background-color: {BG_SECONDARY};
            color: {FG_TEXT};
            border: 1px solid {BG_TERTIARY};
            border-radius: 4px;
            padding: 8px;
            font-weight: bold;
        }}
        QPushButton:hover {{
            background-color: {BG_TERTIARY};
            border: 1px solid {ACCENT_COLOR};
        }}
        QPushButton:pressed {{
            background-color: {ACCENT_COLOR};
            color: {BG_PRIMARY};
        }}
        QSlider::groove:horizontal {{
            border: none;
            background: {BG_TERTIARY};
            height: 6px;
            border-radius: 3px;
        }}
        QSlider::handle:horizontal {{
            background: {ACCENT_COLOR};
            border: none;
            width: 14px;
            margin: -4px 0;
            border-radius: 7px;
        }}
        QSlider::handle:horizontal:hover {{
            background: {ACCENT_HOVER};
        }}
        QCheckBox {{
            color: {FG_TEXT};
            spacing: 8px;
        }}
        QCheckBox::indicator {{
            width: 18px;
            height: 18px;
            border-radius: 3px;
            border: 1px solid {BG_TERTIARY};
            background-color: {BG_SECONDARY};
        }}
        QCheckBox::indicator:checked {{
            background-color: {ACCENT_COLOR};
            image: none;
        }}
        QLabel {{
            color: {FG_TEXT};
        }}
        """
    
    def _on_width_changed(self):
        val = self.width_slider.value()
        self.width_value.setText(str(val))
    
    def _on_contrast_changed(self):
        val = self.contrast_slider.value() / 10.0
        self.contrast_value.setText(f"{val:.1f}×")
    
    def _on_height_changed(self):
        val = self.height_slider.value()
        self.height_value.setText(str(val))
    
    def open_image(self):
        path, _ = QFileDialog.getOpenFileName(self, "Открыть изображение", "",
                                              "Images (*.png *.jpg *.jpeg *.bmp *.gif *.webp);;All files (*)")
        if not path:
            return
        
        self.image_path = path
        try:
            pixmap = QPixmap(path)
            scaled = pixmap.scaledToWidth(318, Qt.TransformationMode.SmoothTransformation)
            if scaled.height() > 238:
                scaled = scaled.scaledToHeight(238, Qt.TransformationMode.SmoothTransformation)
            self.preview_label.setPixmap(scaled)
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось открыть изображение:\n{e}")
    
    def convert(self):
        if not self.image_path:
            QMessageBox.warning(self, "Внимание", "Сначала откройте изображение.")
            return
        
        try:
            self.btn_convert.setEnabled(False)
            self.btn_convert.setText("⏳ Обработка...")
            
            width = self.width_slider.value()
            height = self.height_slider.value()
            invert = self.invert_check.isChecked()
            contrast = self.contrast_slider.value() / 10.0
            
            ascii_art = image_to_ascii(self.image_path, width=width, height=height, invert=invert, contrast=contrast)
            self.text_edit.setPlainText(ascii_art)
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось конвертировать изображение:\n{e}")
        finally:
            self.btn_convert.setEnabled(True)
            self.btn_convert.setText("⚡ Конвертировать")
    
    def save_ascii(self):
        text = self.text_edit.toPlainText()
        if not text.strip():
            QMessageBox.information(self, "Пусто", "Нечего сохранять. Сначала конвертируйте изображение.")
            return
        
        path, _ = QFileDialog.getSaveFileName(self, "Сохранить ASCII-арт", "",
                                              "Text files (*.txt);;All files (*)")
        if not path:
            return
        
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(text)
            QMessageBox.information(self, "Сохранено", f"ASCII-арт сохранён в {path}")
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить файл:\n{e}")
    
    def copy_to_clipboard(self):
        text = self.text_edit.toPlainText()
        if not text.strip():
            return
        
        clipboard = QApplication.clipboard()
        clipboard.setText(text)
        QMessageBox.information(self, "Скопировано", "ASCII-арт скопирован в буфер обмена.")


def main():
    app = QApplication(sys.argv)
    window = ASCIIApp()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

